From 4788c6dcf118c5d4b0dd3540601cdd8ab299f588 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Wed, 2 Oct 2024 17:31:41 -0700 Subject: [PATCH 01/15] feat: begins the work --- app/frontend/Components/Menu/MenuItem.css.ts | 3 +- app/frontend/Components/Menu/MenuItem.tsx | 2 -- .../Pages/Screens/Edit/EditControls/index.tsx | 2 +- .../Screens/Edit/NewControlMenu/index.tsx | 29 +++++++------------ .../ScreenTabControls/EditScreenTabButton.tsx | 2 +- app/frontend/Pages/Screens/Edit/index.tsx | 1 + 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/app/frontend/Components/Menu/MenuItem.css.ts b/app/frontend/Components/Menu/MenuItem.css.ts index c9726d4..202bc20 100644 --- a/app/frontend/Components/Menu/MenuItem.css.ts +++ b/app/frontend/Components/Menu/MenuItem.css.ts @@ -3,11 +3,10 @@ import { css } from '@linaria/core' export const menuItem = css` &.disabled * { - // color: vars.colors.gray[vars.fn.primaryShade()], text-decoration: line-through; & input[type=checkbox], & input[type=checkbox]:checked { - // background-color: vars.colors.gray[vars.fn.primaryShade()], + } } ` diff --git a/app/frontend/Components/Menu/MenuItem.tsx b/app/frontend/Components/Menu/MenuItem.tsx index e77b39d..be9a09f 100644 --- a/app/frontend/Components/Menu/MenuItem.tsx +++ b/app/frontend/Components/Menu/MenuItem.tsx @@ -19,8 +19,6 @@ const MenuItem = forwardRef(( ref={ ref } disabled={ disabled } className={ cx(classes.menuItem, className, { disabled }) } - // component={ href !== undefined ? Link : 'button' } - // href={ href } { ...props } > diff --git a/app/frontend/Pages/Screens/Edit/EditControls/index.tsx b/app/frontend/Pages/Screens/Edit/EditControls/index.tsx index cdb411f..3046926 100644 --- a/app/frontend/Pages/Screens/Edit/EditControls/index.tsx +++ b/app/frontend/Pages/Screens/Edit/EditControls/index.tsx @@ -1,12 +1,12 @@ import React from 'react' import { DndContext, - type DragEndEvent, useSensors, PointerSensor, useSensor, closestCenter, UniqueIdentifier, + type DragEndEvent, } from '@dnd-kit/core' import { arrayMove, diff --git a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx index b93da93..24beb02 100644 --- a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx +++ b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Affix, Button, Menu } from '@/Components' -import ControlForm from '../../../../Features/Control/Form' +import ControlForm from '@/Features/Control/Form' import { modals } from '@mantine/modals' import { Routes } from '@/lib' @@ -9,10 +9,12 @@ const controlFormFilter = ['control.id', 'control.command', 'control.updated_at' type ControlType = 'button'|'spacer'|'slider' interface NewControlMenuProps { - menuId: number + menuId?: number } const NewControlMenu = ({ menuId }: NewControlMenuProps) => { + if(!menuId) return <> + const emptyData = (type: ControlType): Schema.ControlsFormData => ({ screen_id: menuId, control_type: type, @@ -36,18 +38,7 @@ const NewControlMenu = ({ menuId }: NewControlMenuProps) => { } const handleNewSpacerClick = () => { - modals.open({ - title: 'Add New Control Button', - children: ( - modals.closeAll() } - filter={ controlFormFilter } - /> - ), - }) + } return ( @@ -57,17 +48,17 @@ const NewControlMenu = ({ menuId }: NewControlMenuProps) => { - Button - - { /* Slider */ } - { /* + {/* Slider */} + Spacer - */ } + diff --git a/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx b/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx index f23d29d..2d888eb 100644 --- a/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx +++ b/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx @@ -7,7 +7,7 @@ import ScreenForm from '../../Form' import { DeleteButton } from '@/Components/Button' interface EditScreenTabButtonProps { - screen: Schema.ScreensEdit + screen: Schema.ScreensOptions onSuccess?: () => void } diff --git a/app/frontend/Pages/Screens/Edit/index.tsx b/app/frontend/Pages/Screens/Edit/index.tsx index 70adf09..d052950 100644 --- a/app/frontend/Pages/Screens/Edit/index.tsx +++ b/app/frontend/Pages/Screens/Edit/index.tsx @@ -105,6 +105,7 @@ const EditScreen = ({ screen, screens }: IEditScreenProps) => { +
) From f446c408b334566f1d9267e9e7caf15b8017a571 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Fri, 4 Oct 2024 09:16:31 -0700 Subject: [PATCH 02/15] fix: fix strong parameters for json column --- app/controllers/api/users_controller.rb | 2 +- app/controllers/users_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb index 4156db0..2ce5ead 100644 --- a/app/controllers/api/users_controller.rb +++ b/app/controllers/api/users_controller.rb @@ -1,7 +1,7 @@ class Api::UsersController < Api::ApiController expose :user - strong_params :user, permit: [:email, :password, :active, :first_name, :last_name, :number, :table_preferences, :user_preferences] + strong_params :user, permit: [:email, :password, :active, :first_name, :last_name, :number, table_preferences: {}, user_preferences: {}] # @route PATCH /api/users/:id (api_user) # @route PUT /api/users/:id (api_user) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 33e3c46..933a9ae 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -4,7 +4,7 @@ class UsersController < ApplicationController sortable_fields %w(email active first_name last_name number) - strong_params :user, permit: [:email, :password, :active, :first_name, :last_name, :number] + strong_params :user, permit: [:email, :password, :active, :first_name, :last_name, :number, user_preferences: {}] # @route GET /users (users) def index From 09c29d538ecdd21670e78abf6e331c5085e455b0 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Fri, 4 Oct 2024 09:47:12 -0700 Subject: [PATCH 03/15] feat: allows adding spacer to screen --- .../Pages/Screens/Edit/NewControlMenu/index.tsx | 13 ++++++++++--- app/frontend/Pages/Screens/Edit/index.tsx | 2 +- .../types/serializers/Controls/FormData.d.ts | 6 +++--- app/models/control.rb | 7 ++++++- app/serializers/controls/form_data_serializer.rb | 5 +++++ 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx index 24beb02..8a26cd0 100644 --- a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx +++ b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx @@ -3,17 +3,21 @@ import { Affix, Button, Menu } from '@/Components' import ControlForm from '@/Features/Control/Form' import { modals } from '@mantine/modals' import { Routes } from '@/lib' +import { useCreateControl } from '@/queries' const controlFormFilter = ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] type ControlType = 'button'|'spacer'|'slider' interface NewControlMenuProps { + screenId: number|false menuId?: number } -const NewControlMenu = ({ menuId }: NewControlMenuProps) => { - if(!menuId) return <> +const NewControlMenu = ({ screenId, menuId }: NewControlMenuProps) => { + const { mutate: createControl } = useCreateControl() + + if(!menuId || !screenId) return <> const emptyData = (type: ControlType): Schema.ControlsFormData => ({ screen_id: menuId, @@ -38,7 +42,10 @@ const NewControlMenu = ({ menuId }: NewControlMenuProps) => { } const handleNewSpacerClick = () => { - + createControl({ + control_type: "spacer", + screen_id: screenId, + }) } return ( diff --git a/app/frontend/Pages/Screens/Edit/index.tsx b/app/frontend/Pages/Screens/Edit/index.tsx index d052950..07027cf 100644 --- a/app/frontend/Pages/Screens/Edit/index.tsx +++ b/app/frontend/Pages/Screens/Edit/index.tsx @@ -102,7 +102,7 @@ const EditScreen = ({ screen, screens }: IEditScreenProps) => { )) } - + diff --git a/app/frontend/types/serializers/Controls/FormData.d.ts b/app/frontend/types/serializers/Controls/FormData.d.ts index bb58ad4..f15b686 100644 --- a/app/frontend/types/serializers/Controls/FormData.d.ts +++ b/app/frontend/types/serializers/Controls/FormData.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 4e173dce70b753b845bc6a24cb9c641a +// TypesFromSerializers CacheKey c8de8d04021d5bb40752cd5964e3c702 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandsFormData from '../Commands/FormData' @@ -14,11 +14,11 @@ declare global { control_type: string max_value?: string | number min_value?: string | number - order: number + order?: number protocol?: ProtocolsFormData protocol_id?: number screen_id: number - title: string + title?: string value?: string | number } } diff --git a/app/models/control.rb b/app/models/control.rb index ad18cfa..c1be49e 100644 --- a/app/models/control.rb +++ b/app/models/control.rb @@ -32,6 +32,7 @@ class Control < ApplicationRecord include PgSearch::Model before_validation :set_unique_order + before_validation :set_spacer_title, if: -> { self.control_type == "spacer" } pg_search_scope( :search, @@ -55,7 +56,7 @@ class Control < ApplicationRecord scope :includes_associated, -> { includes([:protocol, :command]) } - validate :protocol_xor_command + validate :protocol_xor_command, if: -> { self.control_type != "spacer" } validates :order, presence: true private @@ -71,6 +72,10 @@ def next_order_number max_order ? max_order + 1 : 1 end + def set_spacer_title + self.title = "spacer_#{self.order}" + end + def protocol_xor_command return if protocol.blank? ^ command.blank? diff --git a/app/serializers/controls/form_data_serializer.rb b/app/serializers/controls/form_data_serializer.rb index b71f103..0566741 100644 --- a/app/serializers/controls/form_data_serializer.rb +++ b/app/serializers/controls/form_data_serializer.rb @@ -1,4 +1,9 @@ class Controls::FormDataSerializer < ControlSerializer + attributes( + order: { optional: true }, + title: { optional: true }, + ) + belongs_to :protocol, serializer: Protocols::FormDataSerializer, optional: true belongs_to :command, serializer: Commands::FormDataSerializer, optional: true end From e6c6e41a6329447a9b59668a8cc9d4725411c14d Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Fri, 4 Oct 2024 16:30:29 -0700 Subject: [PATCH 04/15] feat: starts on options --- .../Components/Form/Inputs/Radio/Group.tsx | 34 +++++++++++ .../Components/Form/Inputs/Radio/Radio.tsx | 60 +++++++++++++++++++ .../Components/Form/Inputs/Radio/index.tsx | 4 ++ app/frontend/Components/Form/Inputs/index.ts | 8 +-- app/frontend/Components/Inputs/Radio.tsx | 36 +++++++++++ app/frontend/Components/Inputs/index.ts | 3 +- .../Features/Control/Button/index.tsx | 1 + app/frontend/Features/Control/Control.css.ts | 5 ++ app/frontend/Features/Control/Form.tsx | 9 ++- .../Features/Control/Spacer/index.tsx | 8 ++- .../Edit/EditControls/DraggableControl.tsx | 2 +- .../Pages/Screens/Edit/EditControls/index.tsx | 2 +- app/frontend/Pages/Screens/Edit/index.tsx | 12 ++-- app/frontend/Pages/Screens/Show/index.tsx | 6 +- 14 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 app/frontend/Components/Form/Inputs/Radio/Group.tsx create mode 100644 app/frontend/Components/Form/Inputs/Radio/Radio.tsx create mode 100644 app/frontend/Components/Form/Inputs/Radio/index.tsx create mode 100644 app/frontend/Components/Inputs/Radio.tsx diff --git a/app/frontend/Components/Form/Inputs/Radio/Group.tsx b/app/frontend/Components/Form/Inputs/Radio/Group.tsx new file mode 100644 index 0000000..9ca72cf --- /dev/null +++ b/app/frontend/Components/Form/Inputs/Radio/Group.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { useInertiaInput } from 'use-inertia-form' +import { Radio } from '@/Components/Inputs' +import { type RadioGroupProps } from '@mantine/core' + +export interface FormRadioGroupProps extends RadioGroupProps { + name: string + model?: string +} + +const FormRadioGroup = ({ + children, + name, + model, + ...props +}: FormRadioGroupProps) => { + const { value, setValue } = useInertiaInput({ name, model }) + + const handleValueChange = (vals: string) => { + setValue(vals) + } + + return ( + + { children } + + ) +} + +export default FormRadioGroup diff --git a/app/frontend/Components/Form/Inputs/Radio/Radio.tsx b/app/frontend/Components/Form/Inputs/Radio/Radio.tsx new file mode 100644 index 0000000..1ab09a0 --- /dev/null +++ b/app/frontend/Components/Form/Inputs/Radio/Radio.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import { useForm, useInertiaInput, type NestedObject } from 'use-inertia-form' +import ConditionalWrapper from '@/Components/ConditionalWrapper' +import { Field } from '@/Components/Form' +import RadioInput, { type RadioProps } from '@/Components/Inputs/Radio' +import FormRadioGroup from './Group' +import { type BaseFormInputProps } from '..' + +export interface FormRadioProps + extends + Omit, + Omit, 'name'> {} + +const FormRadioComponent = ( + { + value, + onChange, + onBlur, + onFocus, + id, + required, + className, + model, + field = true, + style, + wrapperProps, + errorKey, + defaultValue, + clearErrorsOnChange, + ...props + }: FormRadioProps, +) => { + const form = useForm() + + const handleChange = (e: React.ChangeEvent) => { + onChange?.(value, form) + } + + const handleBlur = () => { + onBlur?.(value, form) + } + + return ( + onFocus?.(value, form) } + style={ [{ padding: '14px 10px' }, style] } + wrapper={ false } + { ...props } + /> + ) +} + +FormRadioComponent.Group = FormRadioGroup + +export default FormRadioComponent diff --git a/app/frontend/Components/Form/Inputs/Radio/index.tsx b/app/frontend/Components/Form/Inputs/Radio/index.tsx new file mode 100644 index 0000000..e8651d2 --- /dev/null +++ b/app/frontend/Components/Form/Inputs/Radio/index.tsx @@ -0,0 +1,4 @@ +export { Radio as GroupedRadio } from '@/Components/Inputs' + +import FormRadioInput from './Radio' +export default FormRadioInput diff --git a/app/frontend/Components/Form/Inputs/index.ts b/app/frontend/Components/Form/Inputs/index.ts index 92464ac..a9aa069 100644 --- a/app/frontend/Components/Form/Inputs/index.ts +++ b/app/frontend/Components/Form/Inputs/index.ts @@ -8,8 +8,9 @@ export { default as HiddenInput } from './HiddenInput' export { default as MultiSelect } from './MultiSelect' export { default as NumberInput } from './NumberInput' export { default as PasswordInput } from './PasswordInput' -export { default as SegmentedControl } from './SegmentedControl' +export { default as Radio } from './Radio' export { default as RichText } from './RichText' +export { default as SegmentedControl } from './SegmentedControl' export { default as Select } from './Select' export { default as SwatchInput } from './SwatchInput' export { default as Switch } from './Switch' @@ -21,10 +22,9 @@ export { GroupedCheckbox, } from './Checkbox' -export type InputConflicts = 'name'|'onChange'|'onBlur'|'onFocus'|'value'|'defaultValue' +export type InputConflicts = 'name' | 'onChange' | 'onBlur' | 'onFocus' | 'value' | 'defaultValue' export interface BaseFormInputProps - extends UseInertiaInputProps -{ + extends UseInertiaInputProps { model?: string errorKey?: string field?: boolean diff --git a/app/frontend/Components/Inputs/Radio.tsx b/app/frontend/Components/Inputs/Radio.tsx new file mode 100644 index 0000000..4146a10 --- /dev/null +++ b/app/frontend/Components/Inputs/Radio.tsx @@ -0,0 +1,36 @@ +import React, { forwardRef } from 'react' +import { Radio, type RadioProps as MantineRadioProps } from '@mantine/core' +import { type BaseInputProps } from '.' +import InputWrapper from './InputWrapper' + +export interface RadioProps extends Omit, BaseInputProps { + value: string +} + +type RadioComponentType = React.ForwardRefExoticComponent< + RadioProps & React.RefAttributes +> & { + Group: typeof Radio.Group +}; + +const RadioComponent: RadioComponentType = forwardRef(( + { id, wrapper, wrapperProps, value, ...props }, + ref, +) => { + const inputId = id ?? value + + return ( + + + + ) +}) as RadioComponentType + +RadioComponent.Group = Radio.Group + +export default RadioComponent diff --git a/app/frontend/Components/Inputs/index.ts b/app/frontend/Components/Inputs/index.ts index bfefcbe..11fd3cd 100644 --- a/app/frontend/Components/Inputs/index.ts +++ b/app/frontend/Components/Inputs/index.ts @@ -9,8 +9,9 @@ export { default as HiddenInput } from './HiddenInput' export { default as MultiSelect } from './MultiSelect' export { default as NumberInput } from './NumberInput' export { default as PasswordInput } from './PasswordInput' -export { default as SegmentedControl } from './SegmentedControl' +export { default as Radio } from './Radio' export { default as RichText } from './RichText' +export { default as SegmentedControl } from './SegmentedControl' export { default as Select } from './Select' export { default as SwatchInput } from './SwatchInput' export { default as Textarea } from './Textarea' diff --git a/app/frontend/Features/Control/Button/index.tsx b/app/frontend/Features/Control/Button/index.tsx index 76b3e49..11756c3 100644 --- a/app/frontend/Features/Control/Button/index.tsx +++ b/app/frontend/Features/Control/Button/index.tsx @@ -19,6 +19,7 @@ const ButtonControl = forwardRef(( key: 'last-button-clicked', defaultValue: undefined, }) + const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault() diff --git a/app/frontend/Features/Control/Control.css.ts b/app/frontend/Features/Control/Control.css.ts index 9bf8931..03346fb 100644 --- a/app/frontend/Features/Control/Control.css.ts +++ b/app/frontend/Features/Control/Control.css.ts @@ -7,3 +7,8 @@ export const lastButtonClicked = css` border: ${highlightBorderPx}px solid ${vars.colors.green[4]}; margin: calc(${vars.spacing.xs} - ${highlightBorderPx}px); ` +export const spacer = css` + /* border-style: dashed; + border-width: 2px; + border-color: ${vars.colors.white}; */ +` diff --git a/app/frontend/Features/Control/Form.tsx b/app/frontend/Features/Control/Form.tsx index f1f4c67..41642e6 100644 --- a/app/frontend/Features/Control/Form.tsx +++ b/app/frontend/Features/Control/Form.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import { Code, Grid, Paper, ScrollArea, Text } from '@/Components' -import { Form, TextInput, Submit, SwatchInput, FormConsumer } from '@/Components/Form' +import { Form, TextInput, Submit, SwatchInput, FormConsumer, Radio } from '@/Components/Form' import { ProtocolDropdown } from '@/Components/Dropdowns' import { useGetProtocol } from '@/queries' import { FormProps } from 'use-inertia-form' @@ -72,6 +72,13 @@ const ControlForm = ({ control, ...props }: ControlFormProps) => { } } + + + + + + + { control?.id ? 'Update' : 'Create' } Control diff --git a/app/frontend/Features/Control/Spacer/index.tsx b/app/frontend/Features/Control/Spacer/index.tsx index 2bd9c52..cd7ed94 100644 --- a/app/frontend/Features/Control/Spacer/index.tsx +++ b/app/frontend/Features/Control/Spacer/index.tsx @@ -2,9 +2,15 @@ import React from 'react' import { Box } from '@/Components' import { type ControlProps } from '..' +import cx from 'clsx' +import * as classes from '../Control.css' + const SpacerControl = ({ edit = false, ...props }: ControlProps) => { return ( - + ) } diff --git a/app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx b/app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx index 0a71e26..f0fa5f7 100644 --- a/app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx +++ b/app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx @@ -9,7 +9,7 @@ import cx from 'clsx' import * as classes from './Control.css' import { router } from '@inertiajs/react' -interface DraggableControlProps extends ControlProps<{edit: true}> {} +interface DraggableControlProps extends ControlProps<{ edit: true }> {} const DraggableControl = ({ control, ...props }: DraggableControlProps) => { const { diff --git a/app/frontend/Pages/Screens/Edit/EditControls/index.tsx b/app/frontend/Pages/Screens/Edit/EditControls/index.tsx index 3046926..3fdd257 100644 --- a/app/frontend/Pages/Screens/Edit/EditControls/index.tsx +++ b/app/frontend/Pages/Screens/Edit/EditControls/index.tsx @@ -35,7 +35,7 @@ const EditControls = ({ screen }: IEditControlsProps) => { color: '', }, }) - const { getData, setData, model: formModel } = useForm<{screen: Schema.ScreensEdit}>() + const { getData, setData, model: formModel } = useForm<{ screen: Schema.ScreensEdit }>() const sensors = useSensors( useSensor(PointerSensor), ) diff --git a/app/frontend/Pages/Screens/Edit/index.tsx b/app/frontend/Pages/Screens/Edit/index.tsx index 07027cf..00e1920 100644 --- a/app/frontend/Pages/Screens/Edit/index.tsx +++ b/app/frontend/Pages/Screens/Edit/index.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { Divider, Page, Tabs } from '@/Components' +import { Divider, Flex, Page, Tabs } from '@/Components' import { Form, Submit } from '@/Components/Form' import { Routes } from '@/lib' import { useLocation } from '@/lib/hooks' @@ -91,10 +91,12 @@ const EditScreen = ({ screen, screens }: IEditScreenProps) => { filter={ ['screen.id', 'screen.slug', 'screen.created_at', 'screen.updated_at'] } remember={ false } > - + + + + + Save Screen Layout ) } @@ -105,7 +107,7 @@ const EditScreen = ({ screen, screens }: IEditScreenProps) => { - +
) diff --git a/app/frontend/Pages/Screens/Show/index.tsx b/app/frontend/Pages/Screens/Show/index.tsx index fa7479e..cfc422e 100644 --- a/app/frontend/Pages/Screens/Show/index.tsx +++ b/app/frontend/Pages/Screens/Show/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Page, Tabs } from '@/Components' +import { Box, Flex, Page, Tabs } from '@/Components' import Control from '../../../Features/Control' import { Routes } from '@/lib' import { useLocation } from '@/lib/hooks' @@ -32,11 +32,11 @@ const ShowScreen = ({ screen, screens }: IShowScreenProps) => { { screens.map(iScreen => ( { iScreen.id === screen.id && - + { screen?.controls?.map(control => ( )) } - + } )) } From 6addb080c0f9d97781837050d7bda0b9e22655d5 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Tue, 8 Oct 2024 18:00:22 -0700 Subject: [PATCH 05/15] fix: cleaning up Control --- app/frontend/Components/Tabs/TabLink.tsx | 2 +- app/frontend/Components/Tabs/UrlTabs.tsx | 25 +++--- .../Components/Tabs/VerticalNavLayout.tsx | 27 +++--- app/frontend/Components/Tabs/index.tsx | 7 +- app/frontend/Features/Control/index.tsx | 88 ------------------- .../AddControlsInterface.css.ts | 0 .../AddControlsInterface/ButtonControl.tsx | 0 .../AddControlsInterface/SliderControl.tsx | 0 .../AddControlsInterface/SpacerControl.tsx | 0 .../AddControlsInterface/index.tsx | 0 .../Features/Controls/Control/Button/Base.tsx | 29 ++++++ .../Controls/Control/Button/Control.tsx | 52 +++++++++++ .../{ => Controls}/Control/Button/index.tsx | 8 +- .../{ => Controls}/Control/Control.css.ts | 4 + .../{ => Controls}/Control/Slider/index.tsx | 2 +- .../{ => Controls}/Control/Spacer/index.tsx | 4 +- .../Features/Controls/Control/index.tsx | 57 ++++++++++++ .../Features/{ => Controls}/Control/lib.ts | 0 .../EditInterfaceControl}/Control.css.ts | 0 .../EditControlButton.tsx | 9 +- .../EditInterfaceControl/index.tsx} | 10 +-- .../EditControlsInterface}/Form.tsx | 0 .../Controls/EditControlsInterface}/index.tsx | 18 ++-- app/frontend/Features/Controls/index.ts | 4 + app/frontend/Features/index.ts | 2 +- app/frontend/Pages/Commands/Show/index.tsx | 4 +- app/frontend/Pages/Screens/Edit/Form.tsx | 33 +++++++ .../Screens/Edit/NewControlMenu/index.tsx | 8 +- .../ScreenTabControls/EditScreenTabButton.tsx | 2 +- .../ScreenTabControls/NewScreenTabButton.tsx | 2 +- app/frontend/Pages/Screens/Edit/index.tsx | 28 ++---- app/frontend/Pages/Screens/{ => New}/Form.tsx | 2 +- app/frontend/Pages/Screens/New/index.tsx | 4 +- app/frontend/Pages/Screens/Show/index.tsx | 2 +- app/frontend/lib/hooks/index.ts | 1 + app/frontend/lib/hooks/useViewportSize.ts | 16 ++++ .../types/serializers/Commands/Edit.d.ts | 6 +- .../types/serializers/Commands/Index.d.ts | 6 +- .../types/serializers/Commands/Protocol.d.ts | 6 +- .../types/serializers/Commands/Show.d.ts | 6 +- .../types/serializers/Controls/Edit.d.ts | 6 +- .../types/serializers/Controls/Index.d.ts | 6 +- .../types/serializers/Controls/Show.d.ts | 14 +-- .../types/serializers/Protocols/Edit.d.ts | 4 +- .../types/serializers/Protocols/Index.d.ts | 6 +- .../types/serializers/Protocols/Show.d.ts | 4 +- .../serializers/ProtocolsCommands/Edit.d.ts | 6 +- .../serializers/ProtocolsCommands/Index.d.ts | 6 +- .../serializers/ProtocolsCommands/Show.d.ts | 6 +- .../types/serializers/Screens/Edit.d.ts | 6 +- .../types/serializers/Screens/Index.d.ts | 6 +- .../types/serializers/Screens/Show.d.ts | 6 +- .../types/serializers/Servers/Edit.d.ts | 6 +- .../types/serializers/Servers/Index.d.ts | 6 +- .../types/serializers/Servers/Reference.d.ts | 6 +- .../types/serializers/Servers/Show.d.ts | 6 +- .../types/serializers/Users/Edit.d.ts | 6 +- .../types/serializers/Users/Index.d.ts | 6 +- app/serializers/application_serializer.rb | 7 ++ app/serializers/commands/edit_serializer.rb | 4 +- app/serializers/commands/index_serializer.rb | 4 +- .../commands/protocol_serializer.rb | 4 +- app/serializers/commands/show_serializer.rb | 4 +- app/serializers/controls/edit_serializer.rb | 4 +- app/serializers/controls/index_serializer.rb | 4 +- app/serializers/controls/show_serializer.rb | 8 +- app/serializers/protocols/edit_serializer.rb | 2 + app/serializers/protocols/index_serializer.rb | 4 +- app/serializers/protocols/show_serializer.rb | 2 + .../protocols_commands/edit_serializer.rb | 4 +- .../protocols_commands/index_serializer.rb | 4 +- .../protocols_commands/show_serializer.rb | 4 +- app/serializers/screens/edit_serializer.rb | 4 +- app/serializers/screens/index_serializer.rb | 4 +- app/serializers/screens/show_serializer.rb | 4 +- app/serializers/servers/edit_serializer.rb | 4 +- app/serializers/servers/index_serializer.rb | 4 +- .../servers/reference_serializer.rb | 4 +- app/serializers/servers/show_serializer.rb | 4 +- app/serializers/users/edit_serializer.rb | 5 +- app/serializers/users/index_serializer.rb | 2 + app/serializers/users/share_serializer.rb | 4 +- app/serializers/users/show_serializer.rb | 4 +- 83 files changed, 399 insertions(+), 289 deletions(-) delete mode 100644 app/frontend/Features/Control/index.tsx rename app/frontend/Features/{ => Controls}/AddControlsInterface/AddControlsInterface.css.ts (100%) rename app/frontend/Features/{ => Controls}/AddControlsInterface/ButtonControl.tsx (100%) rename app/frontend/Features/{ => Controls}/AddControlsInterface/SliderControl.tsx (100%) rename app/frontend/Features/{ => Controls}/AddControlsInterface/SpacerControl.tsx (100%) rename app/frontend/Features/{ => Controls}/AddControlsInterface/index.tsx (100%) create mode 100644 app/frontend/Features/Controls/Control/Button/Base.tsx create mode 100644 app/frontend/Features/Controls/Control/Button/Control.tsx rename app/frontend/Features/{ => Controls}/Control/Button/index.tsx (83%) rename app/frontend/Features/{ => Controls}/Control/Control.css.ts (90%) rename app/frontend/Features/{ => Controls}/Control/Slider/index.tsx (84%) rename app/frontend/Features/{ => Controls}/Control/Spacer/index.tsx (66%) create mode 100644 app/frontend/Features/Controls/Control/index.tsx rename app/frontend/Features/{ => Controls}/Control/lib.ts (100%) rename app/frontend/{Pages/Screens/Edit/EditControls => Features/Controls/EditControlsInterface/EditInterfaceControl}/Control.css.ts (100%) rename app/frontend/{Pages/Screens/Edit/EditControls => Features/Controls/EditControlsInterface/EditInterfaceControl}/EditControlButton.tsx (79%) rename app/frontend/{Pages/Screens/Edit/EditControls/DraggableControl.tsx => Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx} (80%) rename app/frontend/Features/{Control => Controls/EditControlsInterface}/Form.tsx (100%) rename app/frontend/{Pages/Screens/Edit/EditControls => Features/Controls/EditControlsInterface}/index.tsx (81%) create mode 100644 app/frontend/Features/Controls/index.ts create mode 100644 app/frontend/Pages/Screens/Edit/Form.tsx rename app/frontend/Pages/Screens/{ => New}/Form.tsx (91%) create mode 100644 app/frontend/lib/hooks/useViewportSize.ts diff --git a/app/frontend/Components/Tabs/TabLink.tsx b/app/frontend/Components/Tabs/TabLink.tsx index 29d0bcf..5670e27 100644 --- a/app/frontend/Components/Tabs/TabLink.tsx +++ b/app/frontend/Components/Tabs/TabLink.tsx @@ -3,7 +3,7 @@ import Link, { LinkProps } from '../Link' import cx from 'clsx' interface TabLinkProps extends LinkProps { - position?: undefined|'right' + position?: undefined | 'right' } const TabLink = ({ position, className, ...props }: TabLinkProps) => { diff --git a/app/frontend/Components/Tabs/UrlTabs.tsx b/app/frontend/Components/Tabs/UrlTabs.tsx index e49a3a5..0d0ddef 100644 --- a/app/frontend/Components/Tabs/UrlTabs.tsx +++ b/app/frontend/Components/Tabs/UrlTabs.tsx @@ -1,11 +1,14 @@ -import React, { useEffect, useCallback } from 'react' +import React, { useEffect } from 'react' import { Tabs } from '@mantine/core' import { type VisitOptions } from '@inertiajs/core' import { router } from '@inertiajs/react' -import { ITabsComponentProps } from '.' +import { TabsComponentProps } from '.' import { coerceArray } from '@/lib' +import { useLocation } from '@/lib/hooks' + +const UrlTabs = ({ children, onChange, defaultValue, dependencies, ...props }: TabsComponentProps) => { + const { params } = useLocation() -const UrlTabs = ({ children, onChange, defaultValue, dependencies, ...props }: ITabsComponentProps) => { const navigateTab = (value: string | null, options?: VisitOptions) => { let only: string[] = [] if(value && dependencies?.[value]) { @@ -20,33 +23,29 @@ const UrlTabs = ({ children, onChange, defaultValue, dependencies, ...props }: I }, options || {})) } - const activeTab = useCallback(() => { - const url = new URL(window.location.href) - - return url.searchParams.get('tab') - }, [window.location.href]) + const activeTab = params.get('tab') // Handle direct navigation to tabbed page useEffect(() => { - if(!activeTab() && defaultValue) { + if(!activeTab && defaultValue) { navigateTab(defaultValue, { replace: true }) } else { document.addEventListener('inertia:navigate', function reloadActiveTab() { - navigateTab(activeTab()) + navigateTab(activeTab) document.removeEventListener('inertia:navigate', reloadActiveTab) }) } }, []) const handleTabChange = (value: string | null) => { - navigateTab(value || activeTab()) + navigateTab(value || activeTab) - if(onChange) onChange(value || activeTab()) + if(onChange) onChange(value || activeTab) } return ( { - const { width } = useViewportSize() const theme = useMantineTheme() const [mobileFormat, setMobileFormat] = useState(window.innerWidth < px(theme.breakpoints.sm)) - const location = useLocation() - - useEffect(() => { + useViewportSize(({ width }) => { if(width === 0) return setMobileFormat(width < px(theme.breakpoints.sm)) - }, [width]) + }) + + const location = useLocation() - const handleTabChange = (value: string|null) => { + const handleTabChange = (value: string | null) => { if(!value) return router.get(`${location.pathname}`, { tab: value }, { preserveState: true }) @@ -48,10 +47,12 @@ const VerticalNavLayout = ({ children, tabs, title, routePrefix }: IVerticalNavL > { tabs.map(tab => ( diff --git a/app/frontend/Components/Tabs/index.tsx b/app/frontend/Components/Tabs/index.tsx index b9c3d64..0cf05fe 100644 --- a/app/frontend/Components/Tabs/index.tsx +++ b/app/frontend/Components/Tabs/index.tsx @@ -2,15 +2,16 @@ import React from 'react' import { Tabs, type TabsProps } from '@mantine/core' import UrlTabs from './UrlTabs' import TabLink from './TabLink' + import cx from 'clsx' import * as classes from './Tabs.css' -export interface ITabsComponentProps extends TabsProps { +export interface TabsComponentProps extends TabsProps { urlControlled?: boolean - dependencies?: Record + dependencies?: Record } -const TabsComponent = ({ children, urlControlled = false, className, ...props }: ITabsComponentProps) => { +const TabsComponent = ({ children, urlControlled = false, className, ...props }: TabsComponentProps) => { return urlControlled ? { children } : diff --git a/app/frontend/Features/Control/index.tsx b/app/frontend/Features/Control/index.tsx deleted file mode 100644 index 4448077..0000000 --- a/app/frontend/Features/Control/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { forwardRef } from 'react' -import ButtonControl from './Button' -import SliderControl from './Slider' -import SpacerControl from './Spacer' -import { type BoxProps } from '@mantine/core' - -import cx from 'clsx' -import * as classes from './Control.css' -// import { ConditionalWrapper } from '@/Components' - -interface BaseControlProps extends BoxProps { - children?: React.ReactNode -} - -type ShowControlProps = BaseControlProps & { - edit?: false | undefined - control: Schema.ControlsShow -} - -type EditControlProps = BaseControlProps & { - edit?: true - control: Schema.ControlsFormData -} - -export type ControlProps = - T extends { edit: true } - ? EditControlProps - : ShowControlProps - -const Control = forwardRef(( - { control, edit, className, ...props }, - ref, -) => { - const sharedProps = { - className: cx(className), - ref, - } - - switch(control.control_type) { - case 'button': - return ( - - ) - - case 'slider': - return ( - - ) - - case 'spacer': - return ( - - - ) - - default: - return <> - } -}) - -export default Control - -// const WrappedControl = ({ edit, ...props }) => { -// ( - -// )} -// > - -// -// } diff --git a/app/frontend/Features/AddControlsInterface/AddControlsInterface.css.ts b/app/frontend/Features/Controls/AddControlsInterface/AddControlsInterface.css.ts similarity index 100% rename from app/frontend/Features/AddControlsInterface/AddControlsInterface.css.ts rename to app/frontend/Features/Controls/AddControlsInterface/AddControlsInterface.css.ts diff --git a/app/frontend/Features/AddControlsInterface/ButtonControl.tsx b/app/frontend/Features/Controls/AddControlsInterface/ButtonControl.tsx similarity index 100% rename from app/frontend/Features/AddControlsInterface/ButtonControl.tsx rename to app/frontend/Features/Controls/AddControlsInterface/ButtonControl.tsx diff --git a/app/frontend/Features/AddControlsInterface/SliderControl.tsx b/app/frontend/Features/Controls/AddControlsInterface/SliderControl.tsx similarity index 100% rename from app/frontend/Features/AddControlsInterface/SliderControl.tsx rename to app/frontend/Features/Controls/AddControlsInterface/SliderControl.tsx diff --git a/app/frontend/Features/AddControlsInterface/SpacerControl.tsx b/app/frontend/Features/Controls/AddControlsInterface/SpacerControl.tsx similarity index 100% rename from app/frontend/Features/AddControlsInterface/SpacerControl.tsx rename to app/frontend/Features/Controls/AddControlsInterface/SpacerControl.tsx diff --git a/app/frontend/Features/AddControlsInterface/index.tsx b/app/frontend/Features/Controls/AddControlsInterface/index.tsx similarity index 100% rename from app/frontend/Features/AddControlsInterface/index.tsx rename to app/frontend/Features/Controls/AddControlsInterface/index.tsx diff --git a/app/frontend/Features/Controls/Control/Button/Base.tsx b/app/frontend/Features/Controls/Control/Button/Base.tsx new file mode 100644 index 0000000..051f5a4 --- /dev/null +++ b/app/frontend/Features/Controls/Control/Button/Base.tsx @@ -0,0 +1,29 @@ +import React, { forwardRef } from 'react' +import { Button } from '@/Components' +import axios from 'axios' +import { type ButtonProps } from '@mantine/core' +import { useLocalStorage } from '@/lib/hooks' +import { type ControlProps } from '..' +import { controlRoute, controlTitle } from '../lib' + +import cx from 'clsx' +import * as classes from '../Control.css' + +export interface ButtonControlBaseProps extends ButtonProps, ControlProps {} + +const ButtonControlBase = forwardRef(( + { children, control, disable, ...props }, + ref, +) => { + return ( + + ) +}) + +export default ButtonControlBase diff --git a/app/frontend/Features/Controls/Control/Button/Control.tsx b/app/frontend/Features/Controls/Control/Button/Control.tsx new file mode 100644 index 0000000..bacb6ef --- /dev/null +++ b/app/frontend/Features/Controls/Control/Button/Control.tsx @@ -0,0 +1,52 @@ + +import React, { forwardRef } from 'react' +import { Button } from '@/Components' +import axios from 'axios' +import { type ButtonProps } from '@mantine/core' +import { useLocalStorage } from '@/lib/hooks' +import { type ControlProps } from '..' +import { controlRoute, controlTitle } from '../lib' +import Base from './Base' + +import cx from 'clsx' +import * as classes from '../Control.css' + +export interface ButtonControlProps extends ButtonProps, ControlProps {} + +const ButtonControl = forwardRef(( + { children, control, disable, className, ...props }, + ref, +) => { + const [lastButtonClicked, setLastButtonClicked] = useLocalStorage({ + key: 'last-button-clicked', + defaultValue: undefined, + }) + + const handleButtonClick = (e: React.MouseEvent) => { + e.preventDefault() + + if(disable || !control?.id) return + + const route = controlRoute(control) + + if(!route) return + + axios.put(route) + + setLastButtonClicked(control.id) + } + + return ( + + { children || controlTitle(control) } + + ) +}) + +export default ButtonControl diff --git a/app/frontend/Features/Control/Button/index.tsx b/app/frontend/Features/Controls/Control/Button/index.tsx similarity index 83% rename from app/frontend/Features/Control/Button/index.tsx rename to app/frontend/Features/Controls/Control/Button/index.tsx index 11756c3..a7cfe12 100644 --- a/app/frontend/Features/Control/Button/index.tsx +++ b/app/frontend/Features/Controls/Control/Button/index.tsx @@ -2,17 +2,17 @@ import React, { forwardRef } from 'react' import { Button } from '@/Components' import axios from 'axios' import { type ButtonProps } from '@mantine/core' +import { useLocalStorage } from '@/lib/hooks' import { type ControlProps } from '..' import { controlRoute, controlTitle } from '../lib' -import { useLocalStorage } from '@mantine/hooks' import cx from 'clsx' import * as classes from '../Control.css' -interface ButtonControlProps extends ButtonProps, ControlProps {} +export interface ButtonControlProps extends ButtonProps, ControlProps {} const ButtonControl = forwardRef(( - { children, edit, control, className, ...props }, + { children, control, disable, className, ...props }, ref, ) => { const [lastButtonClicked, setLastButtonClicked] = useLocalStorage({ @@ -23,7 +23,7 @@ const ButtonControl = forwardRef(( const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault() - if(edit || !control?.id) return + if(disable || !control?.id) return const route = controlRoute(control) diff --git a/app/frontend/Features/Control/Control.css.ts b/app/frontend/Features/Controls/Control/Control.css.ts similarity index 90% rename from app/frontend/Features/Control/Control.css.ts rename to app/frontend/Features/Controls/Control/Control.css.ts index 03346fb..8f3c94d 100644 --- a/app/frontend/Features/Control/Control.css.ts +++ b/app/frontend/Features/Controls/Control/Control.css.ts @@ -3,6 +3,10 @@ import { css } from '@linaria/core' const highlightBorderPx = 4 +export const controlWrapper = css` + +` + export const lastButtonClicked = css` border: ${highlightBorderPx}px solid ${vars.colors.green[4]}; margin: calc(${vars.spacing.xs} - ${highlightBorderPx}px); diff --git a/app/frontend/Features/Control/Slider/index.tsx b/app/frontend/Features/Controls/Control/Slider/index.tsx similarity index 84% rename from app/frontend/Features/Control/Slider/index.tsx rename to app/frontend/Features/Controls/Control/Slider/index.tsx index dc60f41..23fab98 100644 --- a/app/frontend/Features/Control/Slider/index.tsx +++ b/app/frontend/Features/Controls/Control/Slider/index.tsx @@ -6,7 +6,7 @@ import { controlRoute, controlTitle } from '../lib' interface SliderControlProps extends SliderProps, ControlProps {} -const SliderControl = ({ edit, control, ...props }: SliderControlProps) => { +const SliderControl = ({ control, ...props }: SliderControlProps) => { const route = controlRoute(control) return ( diff --git a/app/frontend/Features/Control/Spacer/index.tsx b/app/frontend/Features/Controls/Control/Spacer/index.tsx similarity index 66% rename from app/frontend/Features/Control/Spacer/index.tsx rename to app/frontend/Features/Controls/Control/Spacer/index.tsx index cd7ed94..0e4e74b 100644 --- a/app/frontend/Features/Control/Spacer/index.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/index.tsx @@ -5,11 +5,11 @@ import { type ControlProps } from '..' import cx from 'clsx' import * as classes from '../Control.css' -const SpacerControl = ({ edit = false, ...props }: ControlProps) => { +const SpacerControl = ({ className, ...props }: ControlProps) => { return ( ) } diff --git a/app/frontend/Features/Controls/Control/index.tsx b/app/frontend/Features/Controls/Control/index.tsx new file mode 100644 index 0000000..f2ca281 --- /dev/null +++ b/app/frontend/Features/Controls/Control/index.tsx @@ -0,0 +1,57 @@ +import React, { forwardRef } from 'react' +import ButtonControl from './Button' +import SliderControl from './Slider' +import SpacerControl from './Spacer' +import { type BoxProps } from '@mantine/core' +import { ConditionalWrapper, Box } from '@/Components' + +import cx from 'clsx' +import * as classes from './Control.css' + +export interface ControlProps extends BoxProps { + children?: React.ReactNode + control: Schema.ControlsShow | Schema.ControlsEdit + edit?: true | undefined + wrapper?: boolean + disable?: boolean +} + +const Control = ({ control, wrapper = true, className, ...props }: ControlProps) => { + const sharedProps = { + control, + m: "xs", + className: cx(className), + } + + let ControlComponent + + switch(control.control_type) { + case 'button': + ControlComponent = ButtonControl + break; + + /* + case 'slider': + ControlComponent = SliderControl + break; + */ + + case 'spacer': + ControlComponent = SpacerControl + break; + + default: + ControlComponent = React.Fragment + } + + return ( + { children } } + > + + + ) +} + +export default Control diff --git a/app/frontend/Features/Control/lib.ts b/app/frontend/Features/Controls/Control/lib.ts similarity index 100% rename from app/frontend/Features/Control/lib.ts rename to app/frontend/Features/Controls/Control/lib.ts diff --git a/app/frontend/Pages/Screens/Edit/EditControls/Control.css.ts b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/Control.css.ts similarity index 100% rename from app/frontend/Pages/Screens/Edit/EditControls/Control.css.ts rename to app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/Control.css.ts diff --git a/app/frontend/Pages/Screens/Edit/EditControls/EditControlButton.tsx b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx similarity index 79% rename from app/frontend/Pages/Screens/Edit/EditControls/EditControlButton.tsx rename to app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx index 39ae3b4..bea61c0 100644 --- a/app/frontend/Pages/Screens/Edit/EditControls/EditControlButton.tsx +++ b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx @@ -1,15 +1,16 @@ import React from 'react' import { Routes } from '@/lib' -import { Box, type BoxProps } from '@mantine/core' +import { Box } from '@mantine/core' import { EditIcon } from '@/Components/Icons' import { modals } from '@mantine/modals' -import ControlForm from '@/Features/Control/Form' -import { type ControlProps } from '@/Features/Control' +import ControlForm from '@/Features/Controls/EditControlsInterface/Form' +import { type ControlProps } from '@/Features/Controls/Control' import cx from 'clsx' import * as classes from './Control.css' -interface EditControlButtonProps extends BoxProps, ControlProps { +interface EditControlButtonProps extends Omit { + control: Schema.ControlsFormData onSuccess?: () => void } diff --git a/app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx similarity index 80% rename from app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx rename to app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx index f0fa5f7..3cde1d9 100644 --- a/app/frontend/Pages/Screens/Edit/EditControls/DraggableControl.tsx +++ b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Control, { ControlProps } from '@/Features/Control' +import { Control, ControlProps } from '@/Features/Controls' import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { Box } from '@mantine/core' @@ -9,7 +9,7 @@ import cx from 'clsx' import * as classes from './Control.css' import { router } from '@inertiajs/react' -interface DraggableControlProps extends ControlProps<{ edit: true }> {} +interface DraggableControlProps extends ControlProps {} const DraggableControl = ({ control, ...props }: DraggableControlProps) => { const { @@ -35,13 +35,13 @@ const DraggableControl = ({ control, ...props }: DraggableControlProps) => { control={ control } onSuccess={ () => router.reload() } /> - + disable={ true } + wrapper={ false } control={ control } { ...props } /> - ) } diff --git a/app/frontend/Features/Control/Form.tsx b/app/frontend/Features/Controls/EditControlsInterface/Form.tsx similarity index 100% rename from app/frontend/Features/Control/Form.tsx rename to app/frontend/Features/Controls/EditControlsInterface/Form.tsx diff --git a/app/frontend/Pages/Screens/Edit/EditControls/index.tsx b/app/frontend/Features/Controls/EditControlsInterface/index.tsx similarity index 81% rename from app/frontend/Pages/Screens/Edit/EditControls/index.tsx rename to app/frontend/Features/Controls/EditControlsInterface/index.tsx index 3fdd257..09ba459 100644 --- a/app/frontend/Pages/Screens/Edit/EditControls/index.tsx +++ b/app/frontend/Features/Controls/EditControlsInterface/index.tsx @@ -12,14 +12,14 @@ import { arrayMove, SortableContext, } from '@dnd-kit/sortable' -import DraggableControl from './DraggableControl' +import EditInterfaceControl from './EditInterfaceControl' import { useDynamicInputs, useForm } from 'use-inertia-form' -interface IEditControlsProps { +interface EditControlInterfaceProps { screen: Schema.ScreensEdit } -const EditControls = ({ screen }: IEditControlsProps) => { +const EditControlInterface = ({ screen }: EditControlInterfaceProps) => { const { addInput, removeInput, paths } = useDynamicInputs({ model: 'controls', emptyData: { @@ -35,7 +35,9 @@ const EditControls = ({ screen }: IEditControlsProps) => { color: '', }, }) + const { getData, setData, model: formModel } = useForm<{ screen: Schema.ScreensEdit }>() + const sensors = useSensors( useSensor(PointerSensor), ) @@ -45,13 +47,13 @@ const EditControls = ({ screen }: IEditControlsProps) => { const handleDragEnd = ({ active, over }: DragEndEvent) => { if(!over || active.id === over.id) return - const controls = getData(controlsPath) as Schema.ControlsEdit[] + const controls = getData(controlsPath) as Schema.ControlsFormData[] const activeIndex = controls.findIndex(el => el.id === active.id) const overIndex = controls.findIndex(el => el.id === over.id) setData(controlsPath, arrayMove( - getData(controlsPath) as Schema.ControlsEdit[], + getData(controlsPath) as Schema.ControlsFormData[], activeIndex, overIndex, ).map((control, i) => { @@ -70,10 +72,10 @@ const EditControls = ({ screen }: IEditControlsProps) => { items={ getData(`${formModel}.controls`) as UniqueIdentifier[] } > { paths.map((path, i) => { - const record = getData(`${formModel}.${path}`) as Schema.ControlsEdit + const record = getData(`${formModel}.${path}`) as Schema.ControlsFormData return ( - @@ -84,4 +86,4 @@ const EditControls = ({ screen }: IEditControlsProps) => { ) } -export default EditControls +export default EditControlInterface diff --git a/app/frontend/Features/Controls/index.ts b/app/frontend/Features/Controls/index.ts new file mode 100644 index 0000000..e5fd892 --- /dev/null +++ b/app/frontend/Features/Controls/index.ts @@ -0,0 +1,4 @@ +export { default as AddControlsInterface } from './AddControlsInterface' +export { default as Control, type ControlProps } from './Control' +export { default as ControlForm } from './EditControlsInterface/Form' +export { default as EditControlsInterface } from './EditControlsInterface' diff --git a/app/frontend/Features/index.ts b/app/frontend/Features/index.ts index 0c42f00..d0dea7e 100644 --- a/app/frontend/Features/index.ts +++ b/app/frontend/Features/index.ts @@ -1,2 +1,2 @@ -export { default as AddControlsInterface } from './AddControlsInterface' +export { default as AddControlsInterface } from './Controls/AddControlsInterface' export { default as IndexPageTemplate } from './IndexPageTemplate' diff --git a/app/frontend/Pages/Commands/Show/index.tsx b/app/frontend/Pages/Commands/Show/index.tsx index bf38ac9..04a0441 100644 --- a/app/frontend/Pages/Commands/Show/index.tsx +++ b/app/frontend/Pages/Commands/Show/index.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Box, Code, DangerousHtml, Group, Title, Link, Menu, Page, Section, List } from '@/Components' import { Routes } from '@/lib' -import ButtonControl from '@/Features/Control/Button' +import { Control } from '@/Features/Controls' import { EditButton } from '@/Components/Button' interface ShowCommandProps { @@ -42,7 +42,7 @@ const ShowCommand = ({ command }: ShowCommandProps) => { { /* { command?.command_values && <> Test: { command.command_values?.map(value => ( - + )) } } */ } diff --git a/app/frontend/Pages/Screens/Edit/Form.tsx b/app/frontend/Pages/Screens/Edit/Form.tsx new file mode 100644 index 0000000..e87ccda --- /dev/null +++ b/app/frontend/Pages/Screens/Edit/Form.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { Divider, Flex } from '@/Components' +import { Form, Submit } from '@/Components/Form' +import { EditControlsInterface } from '@/Features/Controls' +import { Routes } from '@/lib' + +interface EditScreenFormProps { + screen: Schema.ScreensEdit +} + +const EditScreenForm = ({ screen }: EditScreenFormProps) => { + return ( +
+ + + + + + + + Save Screen Layout + + ) +} + +export default EditScreenForm diff --git a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx index 8a26cd0..575f4d8 100644 --- a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx +++ b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx @@ -1,16 +1,16 @@ import React from 'react' import { Affix, Button, Menu } from '@/Components' -import ControlForm from '@/Features/Control/Form' +import ControlForm from '@/Features/Controls/EditControlsInterface/Form' import { modals } from '@mantine/modals' import { Routes } from '@/lib' import { useCreateControl } from '@/queries' const controlFormFilter = ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] -type ControlType = 'button'|'spacer'|'slider' +type ControlType = 'button' | 'spacer' | 'slider' interface NewControlMenuProps { - screenId: number|false + screenId: number | false menuId?: number } @@ -60,7 +60,7 @@ const NewControlMenu = ({ screenId, menuId }: NewControlMenuProps) => { > Button - {/* Slider */} + { /* Slider */ } diff --git a/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx b/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx index 2d888eb..01a01de 100644 --- a/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx +++ b/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx @@ -3,7 +3,7 @@ import { Routes } from '@/lib' import { Accordion, Box, Divider, Text } from '@/Components' import { EditIcon } from '@/Components/Icons' import { modals } from '@mantine/modals' -import ScreenForm from '../../Form' +import ScreenForm from '../../New/Form' import { DeleteButton } from '@/Components/Button' interface EditScreenTabButtonProps { diff --git a/app/frontend/Pages/Screens/Edit/ScreenTabControls/NewScreenTabButton.tsx b/app/frontend/Pages/Screens/Edit/ScreenTabControls/NewScreenTabButton.tsx index ba198b9..b1497e2 100644 --- a/app/frontend/Pages/Screens/Edit/ScreenTabControls/NewScreenTabButton.tsx +++ b/app/frontend/Pages/Screens/Edit/ScreenTabControls/NewScreenTabButton.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Button } from '@/Components' import { modals } from '@mantine/modals' -import ScreenForm from '../../Form' +import ScreenForm from '../../New/Form' import { Routes } from '@/lib' const NewScreenTabButton = () => { diff --git a/app/frontend/Pages/Screens/Edit/index.tsx b/app/frontend/Pages/Screens/Edit/index.tsx index 00e1920..0501925 100644 --- a/app/frontend/Pages/Screens/Edit/index.tsx +++ b/app/frontend/Pages/Screens/Edit/index.tsx @@ -1,24 +1,23 @@ import React, { useState } from 'react' -import { Divider, Flex, Page, Tabs } from '@/Components' -import { Form, Submit } from '@/Components/Form' +import { Page, Tabs } from '@/Components' import { Routes } from '@/lib' import { useLocation } from '@/lib/hooks' import { router } from '@inertiajs/react' import { useDroppable } from '@dnd-kit/core' -import EditControls from './EditControls' import NewControlMenu from './NewControlMenu' import NewScreenTabButton from './ScreenTabControls/NewScreenTabButton' import EditScreenTabButton from './ScreenTabControls/EditScreenTabButton' import cx from 'clsx' import * as classes from './ScreenControl.css' +import EditScreenForm from './Form' -interface IEditScreenProps { +interface EditScreenProps { screen: Schema.ScreensEdit screens: Schema.ScreensOptions[] } -const EditScreen = ({ screen, screens }: IEditScreenProps) => { +const EditScreen = ({ screen, screens }: EditScreenProps) => { const getScreenId = (slug: string): number | false => { const currentScreen = screens.find(s => s.slug === slug) if(currentScreen?.id) { @@ -82,24 +81,7 @@ const EditScreen = ({ screen, screens }: IEditScreenProps) => { className={ cx(classes.tabsPanel) } ref={ droppable.setNodeRef } > - { iScreen.id === screen.id && ( -
- - - - - - - Save Screen Layout - - ) } + { iScreen.id === screen.id && } )) }
diff --git a/app/frontend/Pages/Screens/Form.tsx b/app/frontend/Pages/Screens/New/Form.tsx similarity index 91% rename from app/frontend/Pages/Screens/Form.tsx rename to app/frontend/Pages/Screens/New/Form.tsx index f028521..d83558e 100644 --- a/app/frontend/Pages/Screens/Form.tsx +++ b/app/frontend/Pages/Screens/New/Form.tsx @@ -10,7 +10,7 @@ type TScreenFormData = { export interface ScreenFormProps { to: string method?: HTTPVerb - onSubmit?: (object: UseFormProps) => boolean|void + onSubmit?: (object: UseFormProps) => boolean | void screen?: Schema.ScreensFormData } diff --git a/app/frontend/Pages/Screens/New/index.tsx b/app/frontend/Pages/Screens/New/index.tsx index 5f11a05..1c76a20 100644 --- a/app/frontend/Pages/Screens/New/index.tsx +++ b/app/frontend/Pages/Screens/New/index.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Title, Page, Section } from '@/Components' import { Routes } from '@/lib' -import ScreenForm from '../Form' +import NewScreenForm from './Form' interface INewScreenProps { screen: Schema.ScreensFormData @@ -16,7 +16,7 @@ const NewScreen = ({ ...data }: INewScreenProps) => {
{ title } - diff --git a/app/frontend/Pages/Screens/Show/index.tsx b/app/frontend/Pages/Screens/Show/index.tsx index cfc422e..83d01b3 100644 --- a/app/frontend/Pages/Screens/Show/index.tsx +++ b/app/frontend/Pages/Screens/Show/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Box, Flex, Page, Tabs } from '@/Components' -import Control from '../../../Features/Control' +import Control from '../../../Features/Controls/Control' import { Routes } from '@/lib' import { useLocation } from '@/lib/hooks' import { router } from '@inertiajs/react' diff --git a/app/frontend/lib/hooks/index.ts b/app/frontend/lib/hooks/index.ts index 8992f5d..764db18 100644 --- a/app/frontend/lib/hooks/index.ts +++ b/app/frontend/lib/hooks/index.ts @@ -7,6 +7,7 @@ export { default as useInit } from './useInit' export { default as useAuth } from './useAuth' export { default as useContrastingTextColor } from './useContrastingTextColor' export { default as useCurrency, type UseCurrencyOptions } from './useCurrency' +export { default as useViewportSize } from './useViewportSize' export * from '@mantine/hooks' diff --git a/app/frontend/lib/hooks/useViewportSize.ts b/app/frontend/lib/hooks/useViewportSize.ts new file mode 100644 index 0000000..0a72820 --- /dev/null +++ b/app/frontend/lib/hooks/useViewportSize.ts @@ -0,0 +1,16 @@ +import { useEffect } from 'react'; +import { useViewportSize as useMantineViewportSize } from '@mantine/hooks'; + +type OnChangeCallback = (dimensions: { width: number, height: number }) => void + +const useViewportSize = (onChange?: OnChangeCallback) => { + const { width, height } = useMantineViewportSize() + + useEffect(() => { + onChange?.({ width, height }) + }, [width, height, onChange]) + + return { width, height } +} + +export default useViewportSize diff --git a/app/frontend/types/serializers/Commands/Edit.d.ts b/app/frontend/types/serializers/Commands/Edit.d.ts index d01d293..ecbc994 100644 --- a/app/frontend/types/serializers/Commands/Edit.d.ts +++ b/app/frontend/types/serializers/Commands/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 06a0205ae660b00672ff8d9160fd0327 +// TypesFromSerializers CacheKey 5cc6f9bfa7a1967ff6291d22f986bbe9 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -10,13 +10,13 @@ declare global { address?: string allow_custom_value: boolean command_values: CommandValue[] - created_at: string | Date + created_at?: string | Date description?: string payload_type: "integer" | "float" | "string" | "blob" | "time" | "symbol" | "character" | "boolean" server_id: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Commands/Index.d.ts b/app/frontend/types/serializers/Commands/Index.d.ts index 76d0683..642d625 100644 --- a/app/frontend/types/serializers/Commands/Index.d.ts +++ b/app/frontend/types/serializers/Commands/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey d783c4e4efc0a84f27f163f96b27531e +// TypesFromSerializers CacheKey da9bc249006e9a82296d1ec5bb9e19fa // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -10,13 +10,13 @@ declare global { address?: string allow_custom_value: boolean command_values: CommandValue[] - created_at: string | Date + created_at?: string | Date description?: string payload_type: "integer" | "float" | "string" | "blob" | "time" | "symbol" | "character" | "boolean" server_id: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Commands/Protocol.d.ts b/app/frontend/types/serializers/Commands/Protocol.d.ts index 31df571..d7a4b28 100644 --- a/app/frontend/types/serializers/Commands/Protocol.d.ts +++ b/app/frontend/types/serializers/Commands/Protocol.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey b201f6ee1d5f5f869a4cb959789fe581 +// TypesFromSerializers CacheKey 511cae9fca07953942ed7b2492896ce3 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -11,14 +11,14 @@ declare global { address?: string allow_custom_value: boolean command_values: CommandValue[] - created_at: string | Date + created_at?: string | Date description?: string payload_type: "integer" | "float" | "string" | "blob" | "time" | "symbol" | "character" | "boolean" server: ServersReference server_id: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Commands/Show.d.ts b/app/frontend/types/serializers/Commands/Show.d.ts index 7f6f861..5ba1d45 100644 --- a/app/frontend/types/serializers/Commands/Show.d.ts +++ b/app/frontend/types/serializers/Commands/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 1b69a9d4543807922a944efcabcd76ba +// TypesFromSerializers CacheKey ebf45c1a0f6e271a90abc2ee6e19f769 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -12,7 +12,7 @@ declare global { address?: string allow_custom_value: boolean command_values: CommandValue[] - created_at: string | Date + created_at?: string | Date description?: string payload_type: "integer" | "float" | "string" | "blob" | "time" | "symbol" | "character" | "boolean" protocols: ProtocolsOptions[] @@ -20,7 +20,7 @@ declare global { server_id: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Controls/Edit.d.ts b/app/frontend/types/serializers/Controls/Edit.d.ts index 7ccd160..608aebc 100644 --- a/app/frontend/types/serializers/Controls/Edit.d.ts +++ b/app/frontend/types/serializers/Controls/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 0770a783eb2bc87c468393c865015831 +// TypesFromSerializers CacheKey acb5f66a04ddf81a59be63a546e2d845 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Command from '../Command' @@ -12,7 +12,7 @@ declare global { command: Command command_id?: number control_type: string - created_at: string | Date + created_at?: string | Date max_value?: string | number min_value?: string | number order: number @@ -20,7 +20,7 @@ declare global { protocol_id?: number screen_id: number title: string - updated_at: string | Date + updated_at?: string | Date value?: string | number } } diff --git a/app/frontend/types/serializers/Controls/Index.d.ts b/app/frontend/types/serializers/Controls/Index.d.ts index fbea870..18bbf05 100644 --- a/app/frontend/types/serializers/Controls/Index.d.ts +++ b/app/frontend/types/serializers/Controls/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 7a569e8cd6a73a61659a0b1a7e0af640 +// TypesFromSerializers CacheKey 633b0ee3e1e20fe309e5a2ce245dbf54 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Command from '../Command' @@ -12,7 +12,7 @@ declare global { command: Command command_id?: number control_type: string - created_at: string | Date + created_at?: string | Date max_value?: string | number min_value?: string | number order: number @@ -20,7 +20,7 @@ declare global { protocol_id?: number screen_id: number title: string - updated_at: string | Date + updated_at?: string | Date value?: string | number } } diff --git a/app/frontend/types/serializers/Controls/Show.d.ts b/app/frontend/types/serializers/Controls/Show.d.ts index 33c4774..90562e4 100644 --- a/app/frontend/types/serializers/Controls/Show.d.ts +++ b/app/frontend/types/serializers/Controls/Show.d.ts @@ -1,26 +1,26 @@ -// TypesFromSerializers CacheKey ef613b82d29f55a49ee7be4ea8492ab2 +// TypesFromSerializers CacheKey beaa26cca3c569597fc1c361e71f4c6f // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. -import type CommandsShow from '../Commands/Show' -import type ProtocolsShow from '../Protocols/Show' +import type Command from '../Command' +import type Protocol from '../Protocol' declare global { namespace Schema { interface ControlsShow { id: number color?: string - command: CommandsShow + command?: Command command_id?: number control_type: string - created_at: string | Date + created_at?: string | Date max_value?: string | number min_value?: string | number order: number - protocol: ProtocolsShow + protocol?: Protocol protocol_id?: number screen_id: number title: string - updated_at: string | Date + updated_at?: string | Date value?: string | number } } diff --git a/app/frontend/types/serializers/Protocols/Edit.d.ts b/app/frontend/types/serializers/Protocols/Edit.d.ts index 0b7d3ae..a699731 100644 --- a/app/frontend/types/serializers/Protocols/Edit.d.ts +++ b/app/frontend/types/serializers/Protocols/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 0bd8d5845e33f82ef98f3902ba585932 +// TypesFromSerializers CacheKey fb55a14753f7b65beba8d19cf2338fc9 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandsFormData from '../Commands/FormData' @@ -9,10 +9,12 @@ declare global { interface ProtocolsEdit { id: number commands: CommandsFormData[] + created_at?: string | Date description?: string protocols_commands: ProtocolsCommandsFormData[] slug: string title: string + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Protocols/Index.d.ts b/app/frontend/types/serializers/Protocols/Index.d.ts index 1474868..9595424 100644 --- a/app/frontend/types/serializers/Protocols/Index.d.ts +++ b/app/frontend/types/serializers/Protocols/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 226dd0c53a183690d3ddf3dad35b557d +// TypesFromSerializers CacheKey cdcd89e4afc0f24eb30a555b5f54ee9a // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandsProtocol from '../Commands/Protocol' @@ -8,11 +8,11 @@ declare global { interface ProtocolsIndex { id: number commands: CommandsProtocol[] - created_at: string | Date + created_at?: string | Date description?: string slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Protocols/Show.d.ts b/app/frontend/types/serializers/Protocols/Show.d.ts index bb6f19b..3f47384 100644 --- a/app/frontend/types/serializers/Protocols/Show.d.ts +++ b/app/frontend/types/serializers/Protocols/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 51d92a1c0febda98424763bb16972478 +// TypesFromSerializers CacheKey 94ecece1d3e3572b6d09eae4bae88b99 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type ProtocolsCommands from './Commands' @@ -8,9 +8,11 @@ declare global { interface ProtocolsShow { id: number commands: ProtocolsCommands[] + created_at?: string | Date description?: string slug: string title: string + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/ProtocolsCommands/Edit.d.ts b/app/frontend/types/serializers/ProtocolsCommands/Edit.d.ts index 704d0e5..32e2ebe 100644 --- a/app/frontend/types/serializers/ProtocolsCommands/Edit.d.ts +++ b/app/frontend/types/serializers/ProtocolsCommands/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 1f1cd7ff0859d9b091bf2e29585701e6 +// TypesFromSerializers CacheKey 8f9ef31ca631d52ac4eb99cb2d22cb58 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -10,11 +10,11 @@ declare global { command_id: number command_value: CommandValue command_value_id?: number - created_at: string | Date + created_at?: string | Date delay?: number order: number protocol_id: number - updated_at: string | Date + updated_at?: string | Date value?: string } } diff --git a/app/frontend/types/serializers/ProtocolsCommands/Index.d.ts b/app/frontend/types/serializers/ProtocolsCommands/Index.d.ts index 79f2561..d1dabb3 100644 --- a/app/frontend/types/serializers/ProtocolsCommands/Index.d.ts +++ b/app/frontend/types/serializers/ProtocolsCommands/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 4cb3aeeb87f65bf9b59517b30ae16365 +// TypesFromSerializers CacheKey 279bfc5be533514a1be4c45bd0d5f806 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -10,11 +10,11 @@ declare global { command_id: number command_value: CommandValue command_value_id?: number - created_at: string | Date + created_at?: string | Date delay?: number order: number protocol_id: number - updated_at: string | Date + updated_at?: string | Date value?: string } } diff --git a/app/frontend/types/serializers/ProtocolsCommands/Show.d.ts b/app/frontend/types/serializers/ProtocolsCommands/Show.d.ts index 89ad2da..f74f4ac 100644 --- a/app/frontend/types/serializers/ProtocolsCommands/Show.d.ts +++ b/app/frontend/types/serializers/ProtocolsCommands/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 667843e03b6448df9d74c8d728d57c0f +// TypesFromSerializers CacheKey b8a0df1e287fb0a00fea24aed26631c6 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandValue from '../CommandValue' @@ -10,11 +10,11 @@ declare global { command_id: number command_value: CommandValue command_value_id?: number - created_at: string | Date + created_at?: string | Date delay?: number order: number protocol_id: number - updated_at: string | Date + updated_at?: string | Date value?: string } } diff --git a/app/frontend/types/serializers/Screens/Edit.d.ts b/app/frontend/types/serializers/Screens/Edit.d.ts index 62cd27f..156ebf1 100644 --- a/app/frontend/types/serializers/Screens/Edit.d.ts +++ b/app/frontend/types/serializers/Screens/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 019376dd495ab7941ed4a2901fbcd780 +// TypesFromSerializers CacheKey 3cb6ac7d23bdf9456bc646ae8639f9df // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type ControlsEdit from '../Controls/Edit' @@ -8,11 +8,11 @@ declare global { interface ScreensEdit { id: number controls: ControlsEdit[] - created_at: string | Date + created_at?: string | Date order: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Screens/Index.d.ts b/app/frontend/types/serializers/Screens/Index.d.ts index c9ed970..8ad38dd 100644 --- a/app/frontend/types/serializers/Screens/Index.d.ts +++ b/app/frontend/types/serializers/Screens/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey b2a4396d0b8234e92a0007321119f276 +// TypesFromSerializers CacheKey 7d2ce040b5c287b87bf5c47a3bd65689 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -7,11 +7,11 @@ declare global { namespace Schema { interface ScreensIndex { id: number - created_at: string | Date + created_at?: string | Date order: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Screens/Show.d.ts b/app/frontend/types/serializers/Screens/Show.d.ts index f3502ba..84c3e81 100644 --- a/app/frontend/types/serializers/Screens/Show.d.ts +++ b/app/frontend/types/serializers/Screens/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 969da6b34a7d5fd14e9680af2812e5aa +// TypesFromSerializers CacheKey 7508b37fec58b96447d65307c349f16f // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type ControlsShow from '../Controls/Show' @@ -8,11 +8,11 @@ declare global { interface ScreensShow { id: number controls: ControlsShow[] - created_at: string | Date + created_at?: string | Date order: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Servers/Edit.d.ts b/app/frontend/types/serializers/Servers/Edit.d.ts index 98e3564..0315593 100644 --- a/app/frontend/types/serializers/Servers/Edit.d.ts +++ b/app/frontend/types/serializers/Servers/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey f339f7c6ee20beabffa34c27dd8b94d5 +// TypesFromSerializers CacheKey 7d77e8e2737c172027077cb905762459 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -7,13 +7,13 @@ declare global { namespace Schema { interface ServersEdit { id: number - created_at: string | Date + created_at?: string | Date description?: string hostname?: string port?: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Servers/Index.d.ts b/app/frontend/types/serializers/Servers/Index.d.ts index 848e806..9cd38eb 100644 --- a/app/frontend/types/serializers/Servers/Index.d.ts +++ b/app/frontend/types/serializers/Servers/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 716bc833e9c693f93d1559387bf54345 +// TypesFromSerializers CacheKey a9c0205b686b98e6efb9f9600ad971d7 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -7,13 +7,13 @@ declare global { namespace Schema { interface ServersIndex { id: number - created_at: string | Date + created_at?: string | Date description?: string hostname?: string port?: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Servers/Reference.d.ts b/app/frontend/types/serializers/Servers/Reference.d.ts index 62c5e15..5802b13 100644 --- a/app/frontend/types/serializers/Servers/Reference.d.ts +++ b/app/frontend/types/serializers/Servers/Reference.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 94c79fec6d06b1a04c3b467c3f749357 +// TypesFromSerializers CacheKey 4ae5b1e732eb9c8b762b58f698add65d // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -7,13 +7,13 @@ declare global { namespace Schema { interface ServersReference { id: number - created_at: string | Date + created_at?: string | Date description?: string hostname?: string port?: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Servers/Show.d.ts b/app/frontend/types/serializers/Servers/Show.d.ts index d0a73a6..09abe08 100644 --- a/app/frontend/types/serializers/Servers/Show.d.ts +++ b/app/frontend/types/serializers/Servers/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 1265f32fcf64780e057802ccf4bb49ae +// TypesFromSerializers CacheKey 204899a50e30e05058c42e1ae91ce15d // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -7,13 +7,13 @@ declare global { namespace Schema { interface ServersShow { id: number - created_at: string | Date + created_at?: string | Date description?: string hostname?: string port?: number slug: string title: string - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Users/Edit.d.ts b/app/frontend/types/serializers/Users/Edit.d.ts index 0635a09..98a8590 100644 --- a/app/frontend/types/serializers/Users/Edit.d.ts +++ b/app/frontend/types/serializers/Users/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey d824050cc029c9a7ac8cee20d61b7a05 +// TypesFromSerializers CacheKey 03c2762ad94f1378106a278397892a4b // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Role from '../Role' @@ -8,10 +8,10 @@ declare global { interface UsersEdit { id: number active: boolean - created_at: string | Date + created_at?: string | Date email: string roles: Role[] - updated_at: string | Date + updated_at?: string | Date } } } diff --git a/app/frontend/types/serializers/Users/Index.d.ts b/app/frontend/types/serializers/Users/Index.d.ts index 198f1f7..a6f5c57 100644 --- a/app/frontend/types/serializers/Users/Index.d.ts +++ b/app/frontend/types/serializers/Users/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 1c9066f3858be681676c2ca283b48f0d +// TypesFromSerializers CacheKey dfe821eb987d3754764212ca26efdbea // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type IUserPreferences from '../../IUserPreferences' @@ -9,10 +9,10 @@ declare global { interface UsersIndex { id: number active: boolean - created_at: string | Date + created_at?: string | Date email: string roles: Role[] - updated_at: string | Date + updated_at?: string | Date user_preferences: IUserPreferences } } diff --git a/app/serializers/application_serializer.rb b/app/serializers/application_serializer.rb index 79d4a0a..189f590 100644 --- a/app/serializers/application_serializer.rb +++ b/app/serializers/application_serializer.rb @@ -6,4 +6,11 @@ class ApplicationSerializer < Oj::Serializer def currency_for(obj) obj.cost&.amount&.to_f end + + def self.timestamps + attributes( + :updated_at, + :created_at, + ) + end end diff --git a/app/serializers/commands/edit_serializer.rb b/app/serializers/commands/edit_serializer.rb index 9cbd9c5..5ba4d2f 100644 --- a/app/serializers/commands/edit_serializer.rb +++ b/app/serializers/commands/edit_serializer.rb @@ -2,7 +2,7 @@ class Commands::EditSerializer < CommandSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/commands/index_serializer.rb b/app/serializers/commands/index_serializer.rb index 3d89b51..59f2e00 100644 --- a/app/serializers/commands/index_serializer.rb +++ b/app/serializers/commands/index_serializer.rb @@ -2,7 +2,7 @@ class Commands::IndexSerializer < CommandSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/commands/protocol_serializer.rb b/app/serializers/commands/protocol_serializer.rb index 3b8d239..87ed06e 100644 --- a/app/serializers/commands/protocol_serializer.rb +++ b/app/serializers/commands/protocol_serializer.rb @@ -2,10 +2,10 @@ class Commands::ProtocolSerializer < CommandSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + self.timestamps + belongs_to :server, serializer: Servers::ReferenceSerializer has_many :command_values, serializer: CommandValueSerializer end diff --git a/app/serializers/commands/show_serializer.rb b/app/serializers/commands/show_serializer.rb index be56dbd..a198e65 100644 --- a/app/serializers/commands/show_serializer.rb +++ b/app/serializers/commands/show_serializer.rb @@ -2,10 +2,10 @@ class Commands::ShowSerializer < CommandSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + self.timestamps + belongs_to :server, serializer: Servers::ReferenceSerializer has_many :protocols, serializer: Protocols::OptionsSerializer end diff --git a/app/serializers/controls/edit_serializer.rb b/app/serializers/controls/edit_serializer.rb index 5f973a2..1859db9 100644 --- a/app/serializers/controls/edit_serializer.rb +++ b/app/serializers/controls/edit_serializer.rb @@ -2,9 +2,9 @@ class Controls::EditSerializer < ControlSerializer attributes( :id, - :updated_at, - :created_at, ) + self.timestamps + belongs_to :protocol, serializer: Protocols::EditSerializer end diff --git a/app/serializers/controls/index_serializer.rb b/app/serializers/controls/index_serializer.rb index 0179a30..ce8672c 100644 --- a/app/serializers/controls/index_serializer.rb +++ b/app/serializers/controls/index_serializer.rb @@ -1,7 +1,7 @@ class Controls::IndexSerializer < ControlSerializer attributes( :id, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/controls/show_serializer.rb b/app/serializers/controls/show_serializer.rb index 05c3c9c..230586a 100644 --- a/app/serializers/controls/show_serializer.rb +++ b/app/serializers/controls/show_serializer.rb @@ -1,10 +1,10 @@ class Controls::ShowSerializer < ControlSerializer attributes( :id, - :updated_at, - :created_at, ) - belongs_to :protocol, serializer: Protocols::ShowSerializer - belongs_to :command, serializer: Commands::ShowSerializer + self.timestamps + + belongs_to :protocol, serializer: ProtocolSerializer, optional: true + belongs_to :command, serializer: CommandSerializer, optional: true end diff --git a/app/serializers/protocols/edit_serializer.rb b/app/serializers/protocols/edit_serializer.rb index 13e18b5..5526779 100644 --- a/app/serializers/protocols/edit_serializer.rb +++ b/app/serializers/protocols/edit_serializer.rb @@ -4,6 +4,8 @@ class Protocols::EditSerializer < Protocols::FormDataSerializer :slug, ) + self.timestamps + has_many :commands, serializer: Commands::FormDataSerializer # has_many :commands, serializer: Protocols::CommandsSerializer end diff --git a/app/serializers/protocols/index_serializer.rb b/app/serializers/protocols/index_serializer.rb index 872c896..90fb4bf 100644 --- a/app/serializers/protocols/index_serializer.rb +++ b/app/serializers/protocols/index_serializer.rb @@ -2,9 +2,9 @@ class Protocols::IndexSerializer < ProtocolSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + self.timestamps + has_many :commands, serializer: Commands::ProtocolSerializer end diff --git a/app/serializers/protocols/show_serializer.rb b/app/serializers/protocols/show_serializer.rb index 47a4938..2431d84 100644 --- a/app/serializers/protocols/show_serializer.rb +++ b/app/serializers/protocols/show_serializer.rb @@ -4,5 +4,7 @@ class Protocols::ShowSerializer < ProtocolSerializer :slug, ) + self.timestamps + has_many :commands, serializer: Protocols::CommandsSerializer end diff --git a/app/serializers/protocols_commands/edit_serializer.rb b/app/serializers/protocols_commands/edit_serializer.rb index c01c1af..395faa0 100644 --- a/app/serializers/protocols_commands/edit_serializer.rb +++ b/app/serializers/protocols_commands/edit_serializer.rb @@ -2,7 +2,7 @@ class ProtocolsCommands::EditSerializer < ProtocolsCommandSerializer attributes( :id, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/protocols_commands/index_serializer.rb b/app/serializers/protocols_commands/index_serializer.rb index 3ecd2c6..18db8cc 100644 --- a/app/serializers/protocols_commands/index_serializer.rb +++ b/app/serializers/protocols_commands/index_serializer.rb @@ -1,7 +1,7 @@ class ProtocolsCommands::IndexSerializer < ProtocolsCommandSerializer attributes( :id, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/protocols_commands/show_serializer.rb b/app/serializers/protocols_commands/show_serializer.rb index 61e7e64..d790f90 100644 --- a/app/serializers/protocols_commands/show_serializer.rb +++ b/app/serializers/protocols_commands/show_serializer.rb @@ -1,7 +1,7 @@ class ProtocolsCommands::ShowSerializer < ProtocolsCommandSerializer attributes( :id, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/screens/edit_serializer.rb b/app/serializers/screens/edit_serializer.rb index 9199897..80b23d6 100644 --- a/app/serializers/screens/edit_serializer.rb +++ b/app/serializers/screens/edit_serializer.rb @@ -2,9 +2,9 @@ class Screens::EditSerializer < ScreenSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + self.timestamps + has_many :controls, serializer: Controls::EditSerializer end diff --git a/app/serializers/screens/index_serializer.rb b/app/serializers/screens/index_serializer.rb index 3f6dd33..b903a55 100644 --- a/app/serializers/screens/index_serializer.rb +++ b/app/serializers/screens/index_serializer.rb @@ -2,7 +2,7 @@ class Screens::IndexSerializer < ScreenSerializer attributes( :slug, :id, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/screens/show_serializer.rb b/app/serializers/screens/show_serializer.rb index f660aa1..f7ccf30 100644 --- a/app/serializers/screens/show_serializer.rb +++ b/app/serializers/screens/show_serializer.rb @@ -2,9 +2,9 @@ class Screens::ShowSerializer < ScreenSerializer attributes( :slug, :id, - :updated_at, - :created_at, ) + self.timestamps + has_many :controls, serializer: Controls::ShowSerializer end diff --git a/app/serializers/servers/edit_serializer.rb b/app/serializers/servers/edit_serializer.rb index 9e5e4a8..535ffb7 100644 --- a/app/serializers/servers/edit_serializer.rb +++ b/app/serializers/servers/edit_serializer.rb @@ -2,7 +2,7 @@ class Servers::EditSerializer < ServerSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/servers/index_serializer.rb b/app/serializers/servers/index_serializer.rb index aa2bda9..6827581 100644 --- a/app/serializers/servers/index_serializer.rb +++ b/app/serializers/servers/index_serializer.rb @@ -2,7 +2,7 @@ class Servers::IndexSerializer < ServerSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/servers/reference_serializer.rb b/app/serializers/servers/reference_serializer.rb index f04b356..56bee72 100644 --- a/app/serializers/servers/reference_serializer.rb +++ b/app/serializers/servers/reference_serializer.rb @@ -2,7 +2,7 @@ class Servers::ReferenceSerializer < ServerSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/servers/show_serializer.rb b/app/serializers/servers/show_serializer.rb index cc797ad..16fa982 100644 --- a/app/serializers/servers/show_serializer.rb +++ b/app/serializers/servers/show_serializer.rb @@ -2,7 +2,7 @@ class Servers::ShowSerializer < ServerSerializer attributes( :id, :slug, - :updated_at, - :created_at, ) + + self.timestamps end diff --git a/app/serializers/users/edit_serializer.rb b/app/serializers/users/edit_serializer.rb index ca6c688..39ea658 100644 --- a/app/serializers/users/edit_serializer.rb +++ b/app/serializers/users/edit_serializer.rb @@ -1,10 +1,9 @@ class Users::EditSerializer < UserSerializer - attributes( :id, - :updated_at, - :created_at, ) + self.timestamps + has_many :roles, serializer: RoleSerializer end diff --git a/app/serializers/users/index_serializer.rb b/app/serializers/users/index_serializer.rb index 4dbd170..fcf55af 100644 --- a/app/serializers/users/index_serializer.rb +++ b/app/serializers/users/index_serializer.rb @@ -6,5 +6,7 @@ class Users::IndexSerializer < UserSerializer user_preferences: { type: "IUserPreferences" }, ) + self.timestamps + has_many :roles, serializer: RoleSerializer end diff --git a/app/serializers/users/share_serializer.rb b/app/serializers/users/share_serializer.rb index 47a5f4c..bfa8be0 100644 --- a/app/serializers/users/share_serializer.rb +++ b/app/serializers/users/share_serializer.rb @@ -1,11 +1,11 @@ class Users::ShareSerializer < UserSerializer attributes( :id, - :created_at, - :updated_at, user_preferences: { type: "IUserPreferences" }, table_preferences: { type: "ITablePreferences" }, ) + self.timestamps + has_many :roles, serializer: RoleSerializer end diff --git a/app/serializers/users/show_serializer.rb b/app/serializers/users/show_serializer.rb index 9313bb8..31b62b6 100644 --- a/app/serializers/users/show_serializer.rb +++ b/app/serializers/users/show_serializer.rb @@ -1,10 +1,10 @@ class Users::ShowSerializer < UserSerializer attributes( :id, - :updated_at, - :created_at, user_preferences: { type: "IUserPreferences" }, ) + self.timestamps + has_many :roles, serializer: RoleSerializer end From ae7fd038955ee5b9ebbfc195a79a75d677c25906 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Thu, 10 Oct 2024 16:42:00 -0700 Subject: [PATCH 06/15] fix: refactors Control components Moves entire implementation of Control component into Features/Control rather than having some of it live in Pages/Screens/Edit. The DnD controls in EditControlWrapper might still be better suited as part of the Screen edit form. --- app/frontend/Components/Button/index.ts | 5 +- .../Features/Controls/Control/Button/Base.tsx | 25 +++---- .../Controls/Control/Button/Control.tsx | 32 ++++----- .../Features/Controls/Control/Button/Edit.tsx | 10 +++ .../Controls/Control/Button/index.tsx | 58 +++------------ .../Controls/Control/EditControlWrapper.tsx | 71 +++++++++++++++++++ .../Features/Controls/Control/Slider/Base.tsx | 0 .../Controls/Control/Slider/Control.tsx | 0 .../Features/Controls/Control/Slider/Edit.tsx | 0 .../Features/Controls/Control/Spacer/Base.tsx | 19 +++++ .../Controls/Control/Spacer/Control.tsx | 10 +++ .../Features/Controls/Control/Spacer/Edit.tsx | 10 +++ .../Controls/Control/Spacer/index.tsx | 21 +++--- .../Features/Controls/Control/index.tsx | 18 +++-- .../Control.css.ts => Controls.css.ts} | 25 +++++++ .../Form.tsx => EditControlForm.tsx} | 14 ++-- .../EditInterfaceControl/Control.css.ts | 23 ------ .../EditControlButton.tsx | 52 -------------- .../EditInterfaceControl/index.tsx | 48 ------------- app/frontend/Features/Controls/index.ts | 3 +- .../Edit/Form/DndEditControlsInterface.tsx} | 11 +-- .../Screens/Edit/{Form.tsx => Form/index.tsx} | 6 +- .../Screens/Edit/NewControlMenu/index.tsx | 2 +- app/frontend/Pages/Screens/Edit/index.tsx | 2 +- 24 files changed, 227 insertions(+), 238 deletions(-) create mode 100644 app/frontend/Features/Controls/Control/Button/Edit.tsx create mode 100644 app/frontend/Features/Controls/Control/EditControlWrapper.tsx create mode 100644 app/frontend/Features/Controls/Control/Slider/Base.tsx create mode 100644 app/frontend/Features/Controls/Control/Slider/Control.tsx create mode 100644 app/frontend/Features/Controls/Control/Slider/Edit.tsx create mode 100644 app/frontend/Features/Controls/Control/Spacer/Base.tsx create mode 100644 app/frontend/Features/Controls/Control/Spacer/Control.tsx create mode 100644 app/frontend/Features/Controls/Control/Spacer/Edit.tsx rename app/frontend/Features/Controls/{Control/Control.css.ts => Controls.css.ts} (56%) rename app/frontend/Features/Controls/{EditControlsInterface/Form.tsx => EditControlForm.tsx} (86%) delete mode 100644 app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/Control.css.ts delete mode 100644 app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx delete mode 100644 app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx rename app/frontend/{Features/Controls/EditControlsInterface/index.tsx => Pages/Screens/Edit/Form/DndEditControlsInterface.tsx} (87%) rename app/frontend/Pages/Screens/Edit/{Form.tsx => Form/index.tsx} (84%) diff --git a/app/frontend/Components/Button/index.ts b/app/frontend/Components/Button/index.ts index 30db904..d1d1fcb 100644 --- a/app/frontend/Components/Button/index.ts +++ b/app/frontend/Components/Button/index.ts @@ -1,4 +1,7 @@ -import { Button, ActionIcon as IconButton } from '@mantine/core' +import { + Button, + ActionIcon as IconButton, +} from '@mantine/core' export default Button export { IconButton } diff --git a/app/frontend/Features/Controls/Control/Button/Base.tsx b/app/frontend/Features/Controls/Control/Button/Base.tsx index 051f5a4..bce3a8b 100644 --- a/app/frontend/Features/Controls/Control/Button/Base.tsx +++ b/app/frontend/Features/Controls/Control/Button/Base.tsx @@ -1,29 +1,26 @@ -import React, { forwardRef } from 'react' +import React from 'react' import { Button } from '@/Components' -import axios from 'axios' -import { type ButtonProps } from '@mantine/core' -import { useLocalStorage } from '@/lib/hooks' +import { ElementProps, type ButtonProps } from '@mantine/core' import { type ControlProps } from '..' -import { controlRoute, controlTitle } from '../lib' +import { controlTitle } from '../lib' import cx from 'clsx' -import * as classes from '../Control.css' +import * as classes from '../../Controls.css' -export interface ButtonControlBaseProps extends ButtonProps, ControlProps {} +export interface ControlButtonBaseProps + extends ControlProps, ButtonProps, + ElementProps<'button', keyof ButtonProps>{ } -const ButtonControlBase = forwardRef(( - { children, control, disable, ...props }, - ref, -) => { +const ControlButtonBase = ({ children, control, disable, className, ...props }: ControlButtonBaseProps) => { return ( ) -}) +} -export default ButtonControlBase +export default ControlButtonBase diff --git a/app/frontend/Features/Controls/Control/Button/Control.tsx b/app/frontend/Features/Controls/Control/Button/Control.tsx index bacb6ef..720a2a0 100644 --- a/app/frontend/Features/Controls/Control/Button/Control.tsx +++ b/app/frontend/Features/Controls/Control/Button/Control.tsx @@ -1,22 +1,21 @@ - -import React, { forwardRef } from 'react' -import { Button } from '@/Components' +import React from 'react' import axios from 'axios' -import { type ButtonProps } from '@mantine/core' import { useLocalStorage } from '@/lib/hooks' -import { type ControlProps } from '..' -import { controlRoute, controlTitle } from '../lib' -import Base from './Base' +import { controlRoute } from '../lib' +import Base, { type ControlButtonBaseProps } from './Base' import cx from 'clsx' -import * as classes from '../Control.css' +import * as classes from '../../Controls.css' -export interface ButtonControlProps extends ButtonProps, ControlProps {} +export interface ControlButtonProps extends ControlButtonBaseProps {} -const ButtonControl = forwardRef(( - { children, control, disable, className, ...props }, - ref, -) => { +const ControlButton = ({ + children, + control, + disable, + className, + ...props +}: ControlButtonProps) => { const [lastButtonClicked, setLastButtonClicked] = useLocalStorage({ key: 'last-button-clicked', defaultValue: undefined, @@ -42,11 +41,12 @@ const ButtonControl = forwardRef(( className={ cx([className, { [classes.lastButtonClicked]: lastButtonClicked === control.id, }]) } + control={ control } { ...props } > - { children || controlTitle(control) } + { children } ) -}) +} -export default ButtonControl +export default ControlButton diff --git a/app/frontend/Features/Controls/Control/Button/Edit.tsx b/app/frontend/Features/Controls/Control/Button/Edit.tsx new file mode 100644 index 0000000..b07df41 --- /dev/null +++ b/app/frontend/Features/Controls/Control/Button/Edit.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import Base, { type ControlButtonBaseProps } from './Base' + +export interface EditControlButtonProps extends ControlButtonBaseProps {} + +const EditControlButton = ({ ...props }: EditControlButtonProps) => { + return +} + +export default EditControlButton diff --git a/app/frontend/Features/Controls/Control/Button/index.tsx b/app/frontend/Features/Controls/Control/Button/index.tsx index a7cfe12..90bad1f 100644 --- a/app/frontend/Features/Controls/Control/Button/index.tsx +++ b/app/frontend/Features/Controls/Control/Button/index.tsx @@ -1,50 +1,14 @@ -import React, { forwardRef } from 'react' -import { Button } from '@/Components' -import axios from 'axios' -import { type ButtonProps } from '@mantine/core' -import { useLocalStorage } from '@/lib/hooks' -import { type ControlProps } from '..' -import { controlRoute, controlTitle } from '../lib' +import React from 'react' +import { ControlButtonBaseProps } from './Base' +import EditControlButton from './Edit' +import ControlButton from './Control' -import cx from 'clsx' -import * as classes from '../Control.css' +interface ControlButtonProps extends ControlButtonBaseProps{ } -export interface ButtonControlProps extends ButtonProps, ControlProps {} +export default ({ edit, ...props }: ControlButtonProps) => { + return edit ? + + : + +} -const ButtonControl = forwardRef(( - { children, control, disable, className, ...props }, - ref, -) => { - const [lastButtonClicked, setLastButtonClicked] = useLocalStorage({ - key: 'last-button-clicked', - defaultValue: undefined, - }) - - const handleButtonClick = (e: React.MouseEvent) => { - e.preventDefault() - - if(disable || !control?.id) return - - const route = controlRoute(control) - - if(!route) return - - axios.put(route) - - setLastButtonClicked(control.id) - } - - return ( - - ) -}) - -export default ButtonControl diff --git a/app/frontend/Features/Controls/Control/EditControlWrapper.tsx b/app/frontend/Features/Controls/Control/EditControlWrapper.tsx new file mode 100644 index 0000000..d52a520 --- /dev/null +++ b/app/frontend/Features/Controls/Control/EditControlWrapper.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import { Routes } from '@/lib' +import { router } from '@inertiajs/react' +import { Control, type ControlProps } from '@/Features/Controls' +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { Box, BoxProps } from '@mantine/core' +import { EditIcon } from '@/Components/Icons' +import { modals } from '@mantine/modals' +import EditControlForm from '../EditControlForm' + +import cx from 'clsx' +import * as classes from '../Controls.css' + +interface EditControlWrapperProps extends ControlProps {} + +const EditControlWrapper = ({ children, control, ...props }: EditControlWrapperProps) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + } = useSortable({ id: control.id! }) + + const handleEditButtonClick = (e: React.MouseEvent) => { + e.stopPropagation() + e.preventDefault() + + modals.open({ + title: 'Edit Control', + children: ( + modals.closeAll() } + filter={ ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] } + // onSuccess={ () => { + // onSuccess?.() + // } } + /> + ), + }) + } + + return ( + + + + + { children } + + ) +} + +export default EditControlWrapper diff --git a/app/frontend/Features/Controls/Control/Slider/Base.tsx b/app/frontend/Features/Controls/Control/Slider/Base.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/frontend/Features/Controls/Control/Slider/Control.tsx b/app/frontend/Features/Controls/Control/Slider/Control.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/frontend/Features/Controls/Control/Slider/Edit.tsx b/app/frontend/Features/Controls/Control/Slider/Edit.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/frontend/Features/Controls/Control/Spacer/Base.tsx b/app/frontend/Features/Controls/Control/Spacer/Base.tsx new file mode 100644 index 0000000..7c652df --- /dev/null +++ b/app/frontend/Features/Controls/Control/Spacer/Base.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { Box } from '@/Components' +import { type ControlProps } from '..' + +import cx from 'clsx' +import * as classes from '../../Controls.css' + +export interface ControlSpacerBaseProps extends ControlProps{ } + +const ControlSpacerBase = ({ className, ...props }: ControlSpacerBaseProps) => { + return ( + + ) +} + +export default ControlSpacerBase diff --git a/app/frontend/Features/Controls/Control/Spacer/Control.tsx b/app/frontend/Features/Controls/Control/Spacer/Control.tsx new file mode 100644 index 0000000..2934f46 --- /dev/null +++ b/app/frontend/Features/Controls/Control/Spacer/Control.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import Base, { ControlSpacerBaseProps } from './Base' + +export interface ControlSpacerProps extends ControlSpacerBaseProps{ } + +const ControlSpacer = ({ ...props }: ControlSpacerProps) => { + return +} + +export default ControlSpacer diff --git a/app/frontend/Features/Controls/Control/Spacer/Edit.tsx b/app/frontend/Features/Controls/Control/Spacer/Edit.tsx new file mode 100644 index 0000000..627dd95 --- /dev/null +++ b/app/frontend/Features/Controls/Control/Spacer/Edit.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import Base, { type ControlSpacerBaseProps } from './Base' + +export interface EditControlSpacerProps extends ControlSpacerBaseProps {} + +const EditControlSpacer = ({ ...props }: EditControlSpacerProps) => { + return +} + +export default EditControlSpacer diff --git a/app/frontend/Features/Controls/Control/Spacer/index.tsx b/app/frontend/Features/Controls/Control/Spacer/index.tsx index 0e4e74b..7522050 100644 --- a/app/frontend/Features/Controls/Control/Spacer/index.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/index.tsx @@ -1,17 +1,14 @@ import React from 'react' -import { Box } from '@/Components' -import { type ControlProps } from '..' +import { ControlSpacerBaseProps } from './Base' +import EditControlSpacer from './Edit' +import ControlSpacer from './Control' -import cx from 'clsx' -import * as classes from '../Control.css' +interface ControlSpacerProps extends ControlSpacerBaseProps{ } -const SpacerControl = ({ className, ...props }: ControlProps) => { - return ( - - ) +export default ({ edit, disable, ...props }: ControlSpacerProps) => { + return edit ? + + : + } -export default SpacerControl diff --git a/app/frontend/Features/Controls/Control/index.tsx b/app/frontend/Features/Controls/Control/index.tsx index f2ca281..b004ecb 100644 --- a/app/frontend/Features/Controls/Control/index.tsx +++ b/app/frontend/Features/Controls/Control/index.tsx @@ -1,12 +1,13 @@ -import React, { forwardRef } from 'react' +import React from 'react' import ButtonControl from './Button' -import SliderControl from './Slider' +// import SliderControl from './Slider' import SpacerControl from './Spacer' import { type BoxProps } from '@mantine/core' import { ConditionalWrapper, Box } from '@/Components' +import EditControlWrapper from './EditControlWrapper' import cx from 'clsx' -import * as classes from './Control.css' +import * as classes from '../Controls.css' export interface ControlProps extends BoxProps { children?: React.ReactNode @@ -16,7 +17,7 @@ export interface ControlProps extends BoxProps { disable?: boolean } -const Control = ({ control, wrapper = true, className, ...props }: ControlProps) => { +const Control = ({ control, edit, wrapper = true, className, ...props }: ControlProps) => { const sharedProps = { control, m: "xs", @@ -46,10 +47,15 @@ const Control = ({ control, wrapper = true, className, ...props }: ControlProps) return ( { children } } > - + { children } } + > + + ) } diff --git a/app/frontend/Features/Controls/Control/Control.css.ts b/app/frontend/Features/Controls/Controls.css.ts similarity index 56% rename from app/frontend/Features/Controls/Control/Control.css.ts rename to app/frontend/Features/Controls/Controls.css.ts index 8f3c94d..f6382a8 100644 --- a/app/frontend/Features/Controls/Control/Control.css.ts +++ b/app/frontend/Features/Controls/Controls.css.ts @@ -16,3 +16,28 @@ export const spacer = css` border-width: 2px; border-color: ${vars.colors.white}; */ ` + +export const button = css` + +` + +export const editButtonIcon = css` + position: absolute; + top: 5px; + right: 13px; + cursor: pointer; + z-index: 100; +` + +export const editControl = css` + cursor: auto; + + &:active { + transform: none; + } +` + +export const editControlWrapper = css` + display: inline-block; + position: relative; +` diff --git a/app/frontend/Features/Controls/EditControlsInterface/Form.tsx b/app/frontend/Features/Controls/EditControlForm.tsx similarity index 86% rename from app/frontend/Features/Controls/EditControlsInterface/Form.tsx rename to app/frontend/Features/Controls/EditControlForm.tsx index 41642e6..f8e418f 100644 --- a/app/frontend/Features/Controls/EditControlsInterface/Form.tsx +++ b/app/frontend/Features/Controls/EditControlForm.tsx @@ -5,15 +5,15 @@ import { ProtocolDropdown } from '@/Components/Dropdowns' import { useGetProtocol } from '@/queries' import { FormProps } from 'use-inertia-form' -type ControlFormData = { +type EditControlFormData = { control: Schema.ControlsFormData } -export interface ControlFormProps extends Omit, 'data'> { +export interface EditControlFormProps extends Omit, 'data'> { control?: Schema.ControlsFormData } -const ControlForm = ({ control, ...props }: ControlFormProps) => { +const EditControlForm = ({ control, ...props }: EditControlFormProps) => { const [showingProtocolSlug, setShowingProtocolSlug] = useState(control?.protocol?.slug || '') const { data } = useGetProtocol({ slug: showingProtocolSlug }, { @@ -22,7 +22,7 @@ const ControlForm = ({ control, ...props }: ControlFormProps) => { }) return ( - + model="control" data={ control ? { control } : undefined } remember={ false } @@ -37,7 +37,7 @@ const ControlForm = ({ control, ...props }: ControlFormProps) => { } - >{ ({ data }) => <> + >{ ({ data }) => <> { data.control.control_type !== 'spacer' && { @@ -65,7 +65,7 @@ const ControlForm = ({ control, ...props }: ControlFormProps) => { } - >{ ({ data }) => <> + >{ ({ data }) => <> { data.control.control_type !== 'spacer' && @@ -87,4 +87,4 @@ const ControlForm = ({ control, ...props }: ControlFormProps) => { ) } -export default ControlForm +export default EditControlForm diff --git a/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/Control.css.ts b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/Control.css.ts deleted file mode 100644 index bfb971f..0000000 --- a/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/Control.css.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { vars } from '@/lib' -import { css } from '@linaria/core' - -export const editButtonIcon = css` - position: absolute; - top: 5px; - right: 13px; - cursor: pointer; - z-index: 100; -` - -export const editControl = css` - cursor: auto; - - &:active { - transform: none; - } -` - -export const editControlWrapper = css` - display: inline-block; - position: relative; -` diff --git a/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx deleted file mode 100644 index bea61c0..0000000 --- a/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/EditControlButton.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Box } from '@mantine/core' -import { EditIcon } from '@/Components/Icons' -import { modals } from '@mantine/modals' -import ControlForm from '@/Features/Controls/EditControlsInterface/Form' -import { type ControlProps } from '@/Features/Controls/Control' - -import cx from 'clsx' -import * as classes from './Control.css' - -interface EditControlButtonProps extends Omit { - control: Schema.ControlsFormData - onSuccess?: () => void -} - -const EditControlButton = ({ control, onSuccess, ...props }: EditControlButtonProps) => { - - const handleEditButtonClick = (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - - modals.open({ - title: 'Edit Control', - children: ( - modals.closeAll() } - filter={ ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] } - onSuccess={ () => { - onSuccess?.() - } } - /> - ), - }) - } - - return ( - - - - ) -} - -export default EditControlButton diff --git a/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx b/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx deleted file mode 100644 index 3cde1d9..0000000 --- a/app/frontend/Features/Controls/EditControlsInterface/EditInterfaceControl/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import { Control, ControlProps } from '@/Features/Controls' -import { useSortable } from '@dnd-kit/sortable' -import { CSS } from '@dnd-kit/utilities' -import { Box } from '@mantine/core' -import EditControlButton from './EditControlButton' - -import cx from 'clsx' -import * as classes from './Control.css' -import { router } from '@inertiajs/react' - -interface DraggableControlProps extends ControlProps {} - -const DraggableControl = ({ control, ...props }: DraggableControlProps) => { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - } = useSortable({ id: control.id! }) - - return ( - - router.reload() } - /> - - disable={ true } - wrapper={ false } - control={ control } - { ...props } - /> - - ) -} - -export default DraggableControl diff --git a/app/frontend/Features/Controls/index.ts b/app/frontend/Features/Controls/index.ts index e5fd892..24fb507 100644 --- a/app/frontend/Features/Controls/index.ts +++ b/app/frontend/Features/Controls/index.ts @@ -1,4 +1,3 @@ export { default as AddControlsInterface } from './AddControlsInterface' export { default as Control, type ControlProps } from './Control' -export { default as ControlForm } from './EditControlsInterface/Form' -export { default as EditControlsInterface } from './EditControlsInterface' +export { default as ControlForm } from './EditControlForm' diff --git a/app/frontend/Features/Controls/EditControlsInterface/index.tsx b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx similarity index 87% rename from app/frontend/Features/Controls/EditControlsInterface/index.tsx rename to app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx index 09ba459..e878a12 100644 --- a/app/frontend/Features/Controls/EditControlsInterface/index.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx @@ -12,14 +12,14 @@ import { arrayMove, SortableContext, } from '@dnd-kit/sortable' -import EditInterfaceControl from './EditInterfaceControl' import { useDynamicInputs, useForm } from 'use-inertia-form' +import { Control } from '@/Features/Controls' -interface EditControlInterfaceProps { +interface DndEditControlsInterfaceProps { screen: Schema.ScreensEdit } -const EditControlInterface = ({ screen }: EditControlInterfaceProps) => { +const DndEditControlsInterface = ({ screen }: DndEditControlsInterfaceProps) => { const { addInput, removeInput, paths } = useDynamicInputs({ model: 'controls', emptyData: { @@ -75,8 +75,9 @@ const EditControlInterface = ({ screen }: EditControlInterfaceProps) => { const record = getData(`${formModel}.${path}`) as Schema.ControlsFormData return ( - ) @@ -86,4 +87,4 @@ const EditControlInterface = ({ screen }: EditControlInterfaceProps) => { ) } -export default EditControlInterface +export default DndEditControlsInterface diff --git a/app/frontend/Pages/Screens/Edit/Form.tsx b/app/frontend/Pages/Screens/Edit/Form/index.tsx similarity index 84% rename from app/frontend/Pages/Screens/Edit/Form.tsx rename to app/frontend/Pages/Screens/Edit/Form/index.tsx index e87ccda..304bfbd 100644 --- a/app/frontend/Pages/Screens/Edit/Form.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/index.tsx @@ -1,8 +1,8 @@ import React from 'react' +import { Routes } from '@/lib' import { Divider, Flex } from '@/Components' import { Form, Submit } from '@/Components/Form' -import { EditControlsInterface } from '@/Features/Controls' -import { Routes } from '@/lib' +import DndEditControlsInterface from './DndEditControlsInterface' interface EditScreenFormProps { screen: Schema.ScreensEdit @@ -20,7 +20,7 @@ const EditScreenForm = ({ screen }: EditScreenFormProps) => { > - + diff --git a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx index 575f4d8..ea7725b 100644 --- a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx +++ b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx @@ -1,9 +1,9 @@ import React from 'react' import { Affix, Button, Menu } from '@/Components' -import ControlForm from '@/Features/Controls/EditControlsInterface/Form' import { modals } from '@mantine/modals' import { Routes } from '@/lib' import { useCreateControl } from '@/queries' +import { ControlForm } from '@/Features/Controls' const controlFormFilter = ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] diff --git a/app/frontend/Pages/Screens/Edit/index.tsx b/app/frontend/Pages/Screens/Edit/index.tsx index 0501925..4074867 100644 --- a/app/frontend/Pages/Screens/Edit/index.tsx +++ b/app/frontend/Pages/Screens/Edit/index.tsx @@ -4,13 +4,13 @@ import { Routes } from '@/lib' import { useLocation } from '@/lib/hooks' import { router } from '@inertiajs/react' import { useDroppable } from '@dnd-kit/core' +import EditScreenForm from './Form' import NewControlMenu from './NewControlMenu' import NewScreenTabButton from './ScreenTabControls/NewScreenTabButton' import EditScreenTabButton from './ScreenTabControls/EditScreenTabButton' import cx from 'clsx' import * as classes from './ScreenControl.css' -import EditScreenForm from './Form' interface EditScreenProps { screen: Schema.ScreensEdit From e356e582b614494d2f5e5168f018019a2b621265 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Tue, 15 Oct 2024 17:23:57 -0700 Subject: [PATCH 07/15] fix: control types seem to work --- .../Components/Dropdowns/ProtocolDropdown.tsx | 12 +++++---- app/frontend/Components/Dropdowns/index.ts | 9 ++++--- .../Controls/Control/EditControlWrapper.tsx | 8 +++--- .../Features/Controls/Control/index.tsx | 27 +++++++++++++++---- .../{EditControlForm.tsx => ControlForm.tsx} | 0 .../AddControlsInterface.css.ts | 0 .../ButtonControl.tsx | 12 ++++----- .../SliderControl.tsx | 12 ++++++--- .../SpacerControl.tsx | 12 ++++++--- .../index.tsx | 0 app/frontend/Features/Controls/index.ts | 4 +-- app/frontend/Features/index.ts | 2 +- app/frontend/Pages/Screens/Show/index.tsx | 2 +- 13 files changed, 66 insertions(+), 34 deletions(-) rename app/frontend/Features/Controls/{EditControlForm.tsx => ControlForm.tsx} (100%) rename app/frontend/Features/Controls/{AddControlsInterface => _AddControlsInterface}/AddControlsInterface.css.ts (100%) rename app/frontend/Features/Controls/{AddControlsInterface => _AddControlsInterface}/ButtonControl.tsx (72%) rename app/frontend/Features/Controls/{AddControlsInterface => _AddControlsInterface}/SliderControl.tsx (75%) rename app/frontend/Features/Controls/{AddControlsInterface => _AddControlsInterface}/SpacerControl.tsx (73%) rename app/frontend/Features/Controls/{AddControlsInterface => _AddControlsInterface}/index.tsx (100%) diff --git a/app/frontend/Components/Dropdowns/ProtocolDropdown.tsx b/app/frontend/Components/Dropdowns/ProtocolDropdown.tsx index 4cad601..1e52016 100644 --- a/app/frontend/Components/Dropdowns/ProtocolDropdown.tsx +++ b/app/frontend/Components/Dropdowns/ProtocolDropdown.tsx @@ -17,11 +17,13 @@ const ProtocolDropdown = ({ ({ - label: command.title, - value: String(command.id), - })) } + options={ !data + ? [] + : data.map(command => ({ + label: command.title, + value: String(command.id), + })) } { ...props } /> ) diff --git a/app/frontend/Components/Dropdowns/CommandPayloadTypesDropdown.tsx b/app/frontend/Components/Dropdowns/CommandPayloadTypesDropdown.tsx index dc14a3e..23f493b 100644 --- a/app/frontend/Components/Dropdowns/CommandPayloadTypesDropdown.tsx +++ b/app/frontend/Components/Dropdowns/CommandPayloadTypesDropdown.tsx @@ -1,12 +1,12 @@ -import React from 'react' -import { Select } from '@/Components/Form' -import { type AsyncDropdown } from '.' -import { useGetCommandPayloadTypes } from '@/queries' +import React from "react" +import { Select } from "@/Components/Form" +import { type AsyncDropdown } from "." +import { useGetCommandPayloadTypes } from "@/queries" const ProtocolDropdown = ( { - label = 'Payload Type', - name = 'payload_type', + label = "Payload Type", + name = "payload_type", initialData = [], value, onSelect, diff --git a/app/frontend/Components/Dropdowns/CommandValueDropdown.tsx b/app/frontend/Components/Dropdowns/CommandValueDropdown.tsx index 9d0dee4..1f6d0b4 100644 --- a/app/frontend/Components/Dropdowns/CommandValueDropdown.tsx +++ b/app/frontend/Components/Dropdowns/CommandValueDropdown.tsx @@ -1,15 +1,15 @@ -import React from 'react' -import { Select } from '@/Components/Form' -import { type AsyncDropdown } from '.' -import { useGetCommand } from '@/queries' +import React from "react" +import { Select } from "@/Components/Form" +import { type AsyncDropdown } from "." +import { useGetCommand } from "@/queries" interface CommandValueDropdownProps extends AsyncDropdown { commandSlug: string } const CommandValueDropdown = ({ - label = 'Command Value', - name = 'command_value_id', + label = "Command Value", + name = "command_value_id", commandSlug, initialData = [], value, @@ -22,11 +22,13 @@ const CommandValueDropdown = ({ ({ - label: server.title!, - value: String(server.id), - })) } + options={ !data + ? [] + : data.map(server => ({ + label: server.title!, + value: String(server.id), + })) } { ...props } /> ) diff --git a/app/frontend/Components/Dropdowns/index.ts b/app/frontend/Components/Dropdowns/index.ts index c20e755..e734fe1 100644 --- a/app/frontend/Components/Dropdowns/index.ts +++ b/app/frontend/Components/Dropdowns/index.ts @@ -1,8 +1,8 @@ -import { ComboboxItem, SelectOption, type FormSelectProps } from '../Form/Inputs/Select' -import { type FormMultiSelectProps } from '../Form/Inputs/MultiSelect' -import { NestedObject, UseFormProps } from 'use-inertia-form' +import { ComboboxItem, SelectOption, type FormSelectProps } from "../Form/Inputs/Select" +import { type FormMultiSelectProps } from "../Form/Inputs/MultiSelect" +import { NestedObject, UseFormProps } from "use-inertia-form" -type FormSelectOmits = 'defaultValue' | 'onBlur' | 'name' | 'onSelect' | 'onChange' +type FormSelectOmits = "defaultValue" | "onBlur" | "name" | "onSelect" | "onChange" export interface AsyncDropdown extends Omit { name?: string label?: string @@ -14,14 +14,14 @@ export interface AsyncDropdown extends Omit onChange?: (protocol: SelectOption | null, options: ComboboxItem[], form: UseFormProps) => void } -export interface AsyncMultiSelect extends Omit { +export interface AsyncMultiSelect extends Omit { errorKey?: string initialData?: T[] } -export { default as ProtocolDropdown } from './ProtocolDropdown' -export { default as ServerDropdown } from './ServerDropdown' -export { default as CommandDropdown } from './CommandDropdown' -export { default as CommandValueDropdown } from './CommandValueDropdown' -export { default as CommandPayloadTypesDropdown } from './CommandPayloadTypesDropdown' +export { default as ProtocolDropdown } from "./ProtocolDropdown" +export { default as ServerDropdown } from "./ServerDropdown" +export { default as CommandDropdown } from "./CommandDropdown" +export { default as CommandValueDropdown } from "./CommandValueDropdown" +export { default as CommandPayloadTypesDropdown } from "./CommandPayloadTypesDropdown" diff --git a/app/frontend/Components/Flash/Flash.tsx b/app/frontend/Components/Flash/Flash.tsx deleted file mode 100644 index 4c167bb..0000000 --- a/app/frontend/Components/Flash/Flash.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useEffect } from 'react' -import { showNotification } from '@mantine/notifications' -import { usePageProps } from '@/lib/hooks' -import { type FlashMessage } from '@/types' - -const Flash = () => { - const { flash } = usePageProps() - - useEffect(() => { - let key: keyof FlashMessage - for(key in flash) { - if(flash[key]) { - let color - switch(key) { - case 'alert': - color = 'red' - break - case 'success': - color = 'green' - break - case 'info': - color = 'blue' - break - case 'warning': - color = 'yellow' - break - } - - showNotification({ - message: flash[key], - color, - }) - } - } - }, [flash]) - - return ( - <> - ) -} - -export default Flash - diff --git a/app/frontend/Components/Flash/index.ts b/app/frontend/Components/Flash/index.ts deleted file mode 100644 index 7e9e89d..0000000 --- a/app/frontend/Components/Flash/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Flash } from './Flash' diff --git a/app/frontend/Components/Flash/index.tsx b/app/frontend/Components/Flash/index.tsx new file mode 100644 index 0000000..dd42c62 --- /dev/null +++ b/app/frontend/Components/Flash/index.tsx @@ -0,0 +1,44 @@ +import { useEffect } from "react" +import { usePage } from "@inertiajs/react" +import { showNotification } from "@mantine/notifications" + +const shownMessageIds = new Set() + +const Flash = () => { + const { flash } = usePage().props + + useEffect(() => { + for(const [type, message] of Object.entries(flash)) { + if(message !== null && !shownMessageIds.has(message.id)) { + let color + switch(type) { + case "alert": + color = "red" + break + case "success": + color = "green" + break + case "info": + color = "blue" + break + case "warning": + color = "yellow" + break + } + + shownMessageIds.add(message.id) + + showNotification({ + id: message.id, + message: message.message, + color, + onClose: () => shownMessageIds.delete(message.id), + }) + } + } + }, [flash]) + + return <> +} + +export default Flash diff --git a/app/frontend/Components/Form/Components/DynamicInputs/NestedField.tsx b/app/frontend/Components/Form/Components/DynamicInputs/NestedField.tsx index ad2590e..f3b970c 100644 --- a/app/frontend/Components/Form/Components/DynamicInputs/NestedField.tsx +++ b/app/frontend/Components/Form/Components/DynamicInputs/NestedField.tsx @@ -1,8 +1,8 @@ -import React, { useMemo, useState } from 'react' -import { Box, Button, Flex, Group, Label, Paper } from '@/Components' -import { PlusCircleIcon, MinusCircleIcon, DraggableIcon } from '@/Components/Icons' -import { NestedFields, NestedObject, useDynamicInputs, useForm } from 'use-inertia-form' -import cx from 'clsx' +import React, { useMemo, useState } from "react" +import { Box, Button, Flex, Group, Label, Paper } from "@/Components" +import { PlusCircleIcon, MinusCircleIcon, DraggableIcon } from "@/Components/Icons" +import { NestedFields, NestedObject, useDynamicInputs, useForm } from "use-inertia-form" +import cx from "clsx" import { DndContext, KeyboardSensor, @@ -14,22 +14,22 @@ import { DragEndEvent, DragStartEvent, UniqueIdentifier, -} from '@dnd-kit/core' +} from "@dnd-kit/core" import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, -} from '@dnd-kit/sortable' -import { CSS } from '@dnd-kit/utilities' +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" -import { createContext } from '@/lib/hooks' -import { FormPointerSensor, FormTouchSensor } from '@/Components/Sortable' -import { type DynamicInputsProps } from '.' +import { createContext } from "@/lib/hooks" +import { FormPointerSensor, FormTouchSensor } from "@/Components/Sortable" +import { type DynamicInputsProps } from "." -import * as classes from '../../Form.css' -import { useDynamicInputContext } from './dynamicInputContext' +import * as classes from "../../Form.css" +import { useDynamicInputContext } from "./dynamicInputContext" interface NestedFieldProps { children: React.ReactNode | React.ReactElement>[] @@ -67,7 +67,7 @@ const NestedField = >({ - + diff --git a/app/frontend/Components/Form/Components/DynamicInputs/SortableDynamicInputs.tsx b/app/frontend/Components/Form/Components/DynamicInputs/SortableDynamicInputs.tsx index 3d49c59..6f4eda3 100644 --- a/app/frontend/Components/Form/Components/DynamicInputs/SortableDynamicInputs.tsx +++ b/app/frontend/Components/Form/Components/DynamicInputs/SortableDynamicInputs.tsx @@ -1,29 +1,29 @@ -import React, { useEffect, useMemo, useState } from 'react' -import { Box, Button, Label } from '@/Components' -import { PlusCircleIcon } from '@/Components/Icons' -import { useDynamicInputs, useForm } from 'use-inertia-form' -import { DynamicInputContextProvider } from './dynamicInputContext' -import cx from 'clsx' +import React, { useEffect, useMemo, useState } from "react" +import { Box, Button, Label } from "@/Components" +import { PlusCircleIcon } from "@/Components/Icons" +import { useDynamicInputs, useForm } from "use-inertia-form" +import { DynamicInputContextProvider } from "./dynamicInputContext" +import cx from "clsx" import { DndContext, useSensor, useSensors, -} from '@dnd-kit/core' +} from "@dnd-kit/core" import { restrictToVerticalAxis, restrictToWindowEdges, -} from '@dnd-kit/modifiers' -import type { Active, DragEndEvent, DragStartEvent } from '@dnd-kit/core' +} from "@dnd-kit/modifiers" +import type { Active, DragEndEvent, DragStartEvent } from "@dnd-kit/core" import { SortableContext, arrayMove, verticalListSortingStrategy, -} from '@dnd-kit/sortable' -import { createContext, useInit } from '@/lib/hooks' -import { FormPointerSensor } from '@/Components/Sortable' -import { type DynamicInputsProps } from '.' -import NestedField from './NestedField' -import { findMax } from '@/lib' +} from "@dnd-kit/sortable" +import { createContext, useInit } from "@/lib/hooks" +import { FormPointerSensor } from "@/Components/Sortable" +import { type DynamicInputsProps } from "." +import NestedField from "./NestedField" +import { findMax } from "@/lib" // import * as classes from '../Form.css' @@ -47,7 +47,7 @@ const SortableDynamicInputs = ({ emptyData, onAddInput, onRemoveInput, - sortField = 'order', + sortField = "order", }: SortableDynamicInputsProps) => { /* Dynamic form stuff */ const { addInput, removeInput, paths } = useDynamicInputs({ model, emptyData }) @@ -63,7 +63,7 @@ const SortableDynamicInputs = ({ const handleAddInput = () => { onAddInput?.() addInput((records: T[]) => { - const lastRecord = findMax(records, 'id') + const lastRecord = findMax(records, "id") return { order: records.length + 1, key: lastRecord?.id + 1, @@ -78,20 +78,20 @@ const SortableDynamicInputs = ({ const target = event.target as HTMLElement - if(target.closest('[data-portal]')) { + if(target.closest("[data-portal]")) { event.stopPropagation() } } - const portals = document.querySelectorAll('[data-portal]') + const portals = document.querySelectorAll("[data-portal]") portals.forEach(portal => { - portal.addEventListener('mousedown', handlePortalClick, { capture: true }) + portal.addEventListener("mousedown", handlePortalClick, { capture: true }) }) return () => { portals.forEach(portal => { - portal.removeEventListener('mousedown', handlePortalClick, { capture: true }) + portal.removeEventListener("mousedown", handlePortalClick, { capture: true }) }) } }) @@ -142,12 +142,12 @@ const SortableDynamicInputs = ({ const datumA = getData(`${formModel}.${pathA}`) as OrderedObject const datumB = getData(`${formModel}.${pathB}`) as OrderedObject - return datumA.order > datumB.order ? 1 : -1 + return datumA.order > datumB.order ? 1 : - 1 }) }, [paths]) return ( - + ({ ) }) } - + diff --git a/app/frontend/Components/Form/Components/DynamicInputs/dynamicInputContext.tsx b/app/frontend/Components/Form/Components/DynamicInputs/dynamicInputContext.tsx index 979b408..96c5f4d 100644 --- a/app/frontend/Components/Form/Components/DynamicInputs/dynamicInputContext.tsx +++ b/app/frontend/Components/Form/Components/DynamicInputs/dynamicInputContext.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react" export interface DynamicInputProps { record: T @@ -15,7 +15,7 @@ export const useDynamicInputContext = (): DynamicInputProps => const context = React.useContext(DynamicInputContext) if(context === null) { - throw new Error('useContext must be inside a Provider with a value') + throw new Error("useContext must be inside a Provider with a value") } return context as DynamicInputProps diff --git a/app/frontend/Components/Form/Components/DynamicInputs/index.tsx b/app/frontend/Components/Form/Components/DynamicInputs/index.tsx index 894ab2f..4e8c245 100644 --- a/app/frontend/Components/Form/Components/DynamicInputs/index.tsx +++ b/app/frontend/Components/Form/Components/DynamicInputs/index.tsx @@ -1,11 +1,11 @@ -import React from 'react' -import { Box, Button, Flex, Label, Paper } from '@/Components' -import { PlusCircleIcon, MinusCircleIcon } from '@/Components/Icons' -import { NestedFields, NestedObject, useDynamicInputs, useForm } from 'use-inertia-form' -import { DynamicInputContextProvider } from './dynamicInputContext' -import cx from 'clsx' +import React from "react" +import { Box, Button, Flex, Label, Paper } from "@/Components" +import { PlusCircleIcon, MinusCircleIcon } from "@/Components/Icons" +import { NestedFields, NestedObject, useDynamicInputs, useForm } from "use-inertia-form" +import { DynamicInputContextProvider } from "./dynamicInputContext" +import cx from "clsx" -import * as classes from '../../Form.css' +import * as classes from "../../Form.css" export interface DynamicInputsProps { children: React.ReactNode | React.ReactElement[] @@ -38,7 +38,7 @@ const DynamicInputs = >({ } return ( - + { paths.map((path, i) => ( @@ -64,7 +64,7 @@ const DynamicInputs = >({ )) } - + @@ -75,4 +75,4 @@ const DynamicInputs = >({ export default DynamicInputs -export { useDynamicInputContext } from './dynamicInputContext' +export { useDynamicInputContext } from "./dynamicInputContext" diff --git a/app/frontend/Components/Form/Components/Field.tsx b/app/frontend/Components/Form/Components/Field.tsx index 8de0b70..9422732 100644 --- a/app/frontend/Components/Form/Components/Field.tsx +++ b/app/frontend/Components/Form/Components/Field.tsx @@ -1,8 +1,8 @@ -import React from 'react' -import { Box, BoxProps } from '@mantine/core' -import cx from 'clsx' -import { useFormFormat } from '../Form' -import { type InputType } from '@/types' +import React from "react" +import { Box, BoxProps } from "@mantine/core" +import cx from "clsx" +import { useFormFormat } from "../Form" +import { type InputType } from "@/types" export interface FieldProps extends BoxProps { children: React.ReactNode @@ -24,11 +24,11 @@ const Field = ({ return ( { children?: (form: UseFormProps) => React.ReactNode diff --git a/app/frontend/Components/Form/Components/FormDataLogger.tsx b/app/frontend/Components/Form/Components/FormDataLogger.tsx index 8e7b891..e92bc22 100644 --- a/app/frontend/Components/Form/Components/FormDataLogger.tsx +++ b/app/frontend/Components/Form/Components/FormDataLogger.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import FormConsumer from './FormConsumer' +import React from "react" +import FormConsumer from "./FormConsumer" const FormDataLogger = () => { return ( diff --git a/app/frontend/Components/Form/Components/FormGroup.tsx b/app/frontend/Components/Form/Components/FormGroup.tsx index 6008bba..cb6c0c4 100644 --- a/app/frontend/Components/Form/Components/FormGroup.tsx +++ b/app/frontend/Components/Form/Components/FormGroup.tsx @@ -1,8 +1,8 @@ -import React from 'react' -import { DivProps } from 'react-html-props' -import { ConditionalWrapper, Grid, Box } from '@/Components' -import { NestedFields } from 'use-inertia-form' -import cx from 'clsx' +import React from "react" +import { DivProps } from "react-html-props" +import { ConditionalWrapper, Grid, Box } from "@/Components" +import { NestedFields } from "use-inertia-form" +import cx from "clsx" interface FormGroupProps extends DivProps { legend?: string @@ -18,7 +18,7 @@ const FormGroup = ({ children, legend, outline = true, model, grid = true }: For wrapper={ children => ( { children } @@ -26,7 +26,7 @@ const FormGroup = ({ children, legend, outline = true, model, grid = true }: For ) } elseWrapper={ children => ( { children } diff --git a/app/frontend/Components/Form/Components/ResetButton.tsx b/app/frontend/Components/Form/Components/ResetButton.tsx index 1f69e25..90ed59b 100644 --- a/app/frontend/Components/Form/Components/ResetButton.tsx +++ b/app/frontend/Components/Form/Components/ResetButton.tsx @@ -1,10 +1,10 @@ -import { Button } from '@/Components' -import { type ButtonProps } from '@mantine/core' -import React from 'react' -import { useForm } from 'use-inertia-form' +import { Button } from "@/Components" +import { type ButtonProps } from "@mantine/core" +import React from "react" +import { useForm } from "use-inertia-form" interface ResetButton extends ButtonProps { - fields?: string|string[] + fields?: string | string[] } const ResetButton = ({ fields, children, ...props }: ResetButton) => { @@ -17,7 +17,7 @@ const ResetButton = ({ fields, children, ...props }: ResetButton) => { } return ( - + ) } diff --git a/app/frontend/Components/Form/Components/Submit.tsx b/app/frontend/Components/Form/Components/Submit.tsx index 48fa0ea..0ce913b 100644 --- a/app/frontend/Components/Form/Components/Submit.tsx +++ b/app/frontend/Components/Form/Components/Submit.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Button, Link } from '@/Components' -import { Submit as SubmitButton, useForm } from 'use-inertia-form' -import { Flex, type ButtonProps } from '@mantine/core' +import React from "react" +import { Button, Link } from "@/Components" +import { Submit as SubmitButton, useForm } from "use-inertia-form" +import { Flex, type ButtonProps } from "@mantine/core" interface SubmitButtonProps extends ButtonProps { cancelRoute?: string diff --git a/app/frontend/Components/Form/Components/index.ts b/app/frontend/Components/Form/Components/index.ts index eec6474..e876299 100644 --- a/app/frontend/Components/Form/Components/index.ts +++ b/app/frontend/Components/Form/Components/index.ts @@ -1,10 +1,10 @@ -export { default as ResetButton } from './ResetButton' -export { default as FormConsumer } from './FormConsumer' -export { default as FormGroup } from './FormGroup' -export { default as Field } from './Field' -export { default as FieldsFor } from './FieldsFor' -export { default as Submit } from './Submit' +export { default as ResetButton } from "./ResetButton" +export { default as FormConsumer } from "./FormConsumer" +export { default as FormGroup } from "./FormGroup" +export { default as Field } from "./Field" +export { default as FieldsFor } from "./FieldsFor" +export { default as Submit } from "./Submit" -export { default as DynamicInputs, useDynamicInputContext } from './DynamicInputs' +export { default as DynamicInputs, useDynamicInputContext } from "./DynamicInputs" -export { default as FormDataLogger } from './FormDataLogger' +export { default as FormDataLogger } from "./FormDataLogger" diff --git a/app/frontend/Components/Form/Form.css.ts b/app/frontend/Components/Form/Form.css.ts index de252e1..63d3928 100644 --- a/app/frontend/Components/Form/Form.css.ts +++ b/app/frontend/Components/Form/Form.css.ts @@ -1,6 +1,6 @@ -import { vars, theme } from '@/lib/theme' -import { rem } from '@mantine/core' -import { css } from '@linaria/core' +import { vars, theme } from "@/lib/theme" +import { rem } from "@mantine/core" +import { css } from "@linaria/core" export const form = css` .field { diff --git a/app/frontend/Components/Form/Form.tsx b/app/frontend/Components/Form/Form.tsx index 76ebe65..56bc4e9 100644 --- a/app/frontend/Components/Form/Form.tsx +++ b/app/frontend/Components/Form/Form.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Box } from '@mantine/core' -import { createContext } from '@/lib/hooks' +import React from "react" +import { Box } from "@mantine/core" +import { createContext } from "@/lib/hooks" import { Form as InertiaForm, type FormProps as UifFormProps, type NestedObject, -} from 'use-inertia-form' +} from "use-inertia-form" -import cx from 'clsx' -import * as classes from './Form.css' +import cx from "clsx" +import * as classes from "./Form.css" type FormLayoutValues = { disableFormatting: boolean @@ -36,7 +36,7 @@ const Form = ({ extends diff --git a/app/frontend/Components/Form/Inputs/Checkbox/Checkbox.tsx b/app/frontend/Components/Form/Inputs/Checkbox/Checkbox.tsx index 7aff5fd..2a2f7e3 100644 --- a/app/frontend/Components/Form/Inputs/Checkbox/Checkbox.tsx +++ b/app/frontend/Components/Form/Inputs/Checkbox/Checkbox.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { useInertiaInput, type NestedObject } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { Field } from '@/Components/Form' -import CheckboxInput, { type CheckboxProps } from '@/Components/Inputs/Checkbox' -import FormCheckboxGroup from './Group' -import { type InputConflicts, type BaseFormInputProps } from '..' +import React from "react" +import { useInertiaInput, type NestedObject } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { Field } from "@/Components/Form" +import CheckboxInput, { type CheckboxProps } from "@/Components/Inputs/Checkbox" +import FormCheckboxGroup from "./Group" +import { type InputConflicts, type BaseFormInputProps } from ".." export interface FormCheckboxProps extends @@ -71,7 +71,7 @@ const FormCheckboxComponent = ( onBlur={ handleBlur } onFocus={ e => onFocus?.(e.target.checked, form) } error={ error } - style={ [{ padding: '14px 10px' }, style] } + style={ [{ padding: "14px 10px" }, style] } wrapper={ false } { ...props } /> diff --git a/app/frontend/Components/Form/Inputs/Checkbox/Group.tsx b/app/frontend/Components/Form/Inputs/Checkbox/Group.tsx index 8ebc5b4..ca09101 100644 --- a/app/frontend/Components/Form/Inputs/Checkbox/Group.tsx +++ b/app/frontend/Components/Form/Inputs/Checkbox/Group.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { useInertiaInput } from 'use-inertia-form' -import { Checkbox } from '@/Components/Inputs' -import { type CheckboxGroupProps } from '@mantine/core' +import React from "react" +import { useInertiaInput } from "use-inertia-form" +import { Checkbox } from "@/Components/Inputs" +import { type CheckboxGroupProps } from "@mantine/core" export interface FormCheckboxGroupProps extends CheckboxGroupProps { name: string diff --git a/app/frontend/Components/Form/Inputs/Checkbox/index.tsx b/app/frontend/Components/Form/Inputs/Checkbox/index.tsx index b096674..b49def6 100644 --- a/app/frontend/Components/Form/Inputs/Checkbox/index.tsx +++ b/app/frontend/Components/Form/Inputs/Checkbox/index.tsx @@ -1,4 +1,4 @@ -export { Checkbox as GroupedCheckbox } from '@/Components/Inputs' +export { Checkbox as GroupedCheckbox } from "@/Components/Inputs" -import FormCheckboxInput from './Checkbox' +import FormCheckboxInput from "./Checkbox" export default FormCheckboxInput diff --git a/app/frontend/Components/Form/Inputs/CurrencyInput.tsx b/app/frontend/Components/Form/Inputs/CurrencyInput.tsx index 0461672..21e8a4b 100644 --- a/app/frontend/Components/Form/Inputs/CurrencyInput.tsx +++ b/app/frontend/Components/Form/Inputs/CurrencyInput.tsx @@ -1,17 +1,16 @@ -import React from 'react' -import CurrencyInput, { type CurrencyInputProps } from '@/Components/Inputs/CurrencyInput' -import Field from '../Components/Field' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { InputConflicts, type BaseFormInputProps } from '.' -import { type Money } from '@/types' -import { useCurrency } from '@/lib/hooks' +import React from "react" +import CurrencyInput, { type CurrencyInputProps } from "@/Components/Inputs/CurrencyInput" +import Field from "../Components/Field" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { InputConflicts, type BaseFormInputProps } from "." +import { type Money } from "@/types" +import { useCurrency } from "@/lib/hooks" interface INumberInputProps extends Omit, - BaseFormInputProps -{ + BaseFormInputProps { } @@ -32,7 +31,7 @@ const FormInput = ( ...props } : INumberInputProps, ) => { - const { form, inputName, inputId, value, setValue, error } = useInertiaInput({ + const { form, inputName, inputId, value, setValue, error } = useInertiaInput({ name, model, errorKey, @@ -44,7 +43,7 @@ const FormInput = ( amount: value, }) - const handleChange = (value: string|number) => { + const handleChange = (value: string | number) => { const numberValue = Number(value) setValue(numberValue) diff --git a/app/frontend/Components/Form/Inputs/DateInput.tsx b/app/frontend/Components/Form/Inputs/DateInput.tsx index c04917f..f09f3d3 100644 --- a/app/frontend/Components/Form/Inputs/DateInput.tsx +++ b/app/frontend/Components/Form/Inputs/DateInput.tsx @@ -1,16 +1,16 @@ -import React from 'react' -import Field from '../Components/Field' -import { DateInput, type DateInputValue } from '@/Components/Inputs' -import { type DateInputProps } from '@/Components/Inputs/DateInput' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' -import { isUnset } from '@/lib' +import React from "react" +import Field from "../Components/Field" +import { DateInput, type DateInputValue } from "@/Components/Inputs" +import { type DateInputProps } from "@/Components/Inputs/DateInput" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." +import { isUnset } from "@/lib" interface FormDateInputProps extends Omit, - BaseFormInputProps | '', TForm> {} + BaseFormInputProps | "", TForm> {} const FormDateInput = ({ name, @@ -29,7 +29,7 @@ const FormDateInput = ({ }: FormDateInputProps, ) => { const { form, inputName, inputId, value, setValue, error } = useInertiaInput< - Exclude | '', + Exclude | "", TForm >({ name, @@ -40,7 +40,7 @@ const FormDateInput = ({ }) const handleChange = (date: DateInputValue) => { - const dateWithValidEmptyType = (isUnset(date) ? '' : date) + const dateWithValidEmptyType = (isUnset(date) ? "" : date) setValue(dateWithValidEmptyType) diff --git a/app/frontend/Components/Form/Inputs/DateTimeInput.tsx b/app/frontend/Components/Form/Inputs/DateTimeInput.tsx index f006cc0..b52cc7a 100644 --- a/app/frontend/Components/Form/Inputs/DateTimeInput.tsx +++ b/app/frontend/Components/Form/Inputs/DateTimeInput.tsx @@ -1,15 +1,15 @@ -import React from 'react' -import Field from '../Components/Field' -import DateTimeInput, { type DateTimeProps } from '@/Components/Inputs/DateTimeInput' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' -import { isUnset } from '@/lib' +import React from "react" +import Field from "../Components/Field" +import DateTimeInput, { type DateTimeProps } from "@/Components/Inputs/DateTimeInput" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." +import { isUnset } from "@/lib" interface DateTimeFormProps extends Omit, - BaseFormInputProps {} + BaseFormInputProps {} const DateTime = ({ name, @@ -27,7 +27,7 @@ const DateTime = ({ ...props }: DateTimeFormProps, ) => { - const { form, inputName, inputId, value, setValue, error } = useInertiaInput({ + const { form, inputName, inputId, value, setValue, error } = useInertiaInput({ name, model, errorKey, @@ -35,8 +35,8 @@ const DateTime = ({ clearErrorsOnChange, }) - const handleChange = (date: Date|null) => { - const dateWithValidEmptyType = (isUnset(date) ? '' : date) + const handleChange = (date: Date | null) => { + const dateWithValidEmptyType = (isUnset(date) ? "" : date) setValue(dateWithValidEmptyType) @@ -68,7 +68,7 @@ const DateTime = ({ extends - Omit, - Omit, 'span'|OmittedHiddenInputProps> {} + Omit, + Omit, "span" | OmittedHiddenInputProps> {} const FormInput = ( { name, model, onChange, id, defaultValue, ...props }: HiddenInputProps, diff --git a/app/frontend/Components/Form/Inputs/MultiSelect.tsx b/app/frontend/Components/Form/Inputs/MultiSelect.tsx index cce3c05..dd89fbe 100644 --- a/app/frontend/Components/Form/Inputs/MultiSelect.tsx +++ b/app/frontend/Components/Form/Inputs/MultiSelect.tsx @@ -1,17 +1,17 @@ -import React from 'react' -import { NestedObject, UseFormProps, useInertiaInput } from 'use-inertia-form' -import { ConditionalWrapper } from '@/Components' -import Field from '../Components/Field' -import MultiSelect, { type MultiSelectInputProps } from '@/Components/Inputs/MultiSelect' -import { type ComboboxData } from '@mantine/core' -import { type InputConflicts, type BaseFormInputProps } from '.' -import { exclude, isUnset } from '@/lib' -import { coerceArray } from '../../../lib/collections' +import React from "react" +import { NestedObject, UseFormProps, useInertiaInput } from "use-inertia-form" +import { ConditionalWrapper } from "@/Components" +import Field from "../Components/Field" +import MultiSelect, { type MultiSelectInputProps } from "@/Components/Inputs/MultiSelect" +import { type ComboboxData } from "@mantine/core" +import { type InputConflicts, type BaseFormInputProps } from "." +import { exclude, isUnset } from "@/lib" +import { coerceArray } from "../../../lib/collections" -type OmittedDropdownTypes = InputConflicts|'onDropdownOpen'|'onDropdownClose'|'onOptionSubmit'|'onClear' +type OmittedDropdownTypes = InputConflicts | "onDropdownOpen" | "onDropdownClose" | "onOptionSubmit" | "onClear" export interface FormMultiSelectProps extends Omit, - Omit, 'onChange'|'onBlur'|'onFocus'> { + Omit, "onChange" | "onBlur" | "onFocus"> { value?: string[] onChange?: (values: string[], options: ComboboxData, form: UseFormProps) => void @@ -115,7 +115,7 @@ const MultiSelectComponent = ( onDropdownClose={ handleDropdownClose } onOptionSubmit={ handleOptionSubmit } wrapper={ false } - { ...exclude(props, 'value') } + { ...exclude(props, "value") } /> ) diff --git a/app/frontend/Components/Form/Inputs/NumberInput.tsx b/app/frontend/Components/Form/Inputs/NumberInput.tsx index f3216fc..7742dc5 100644 --- a/app/frontend/Components/Form/Inputs/NumberInput.tsx +++ b/app/frontend/Components/Form/Inputs/NumberInput.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import NumberInput, { type NumberInputProps } from '@/Components/Inputs/NumberInput' -import Field from '../Components/Field' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import NumberInput, { type NumberInputProps } from "@/Components/Inputs/NumberInput" +import Field from "../Components/Field" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormNumberInputProps extends @@ -35,7 +35,7 @@ const FormInput = ( clearErrorsOnChange, }) - const handleChange = (val: string|number) => { + const handleChange = (val: string | number) => { const v = Number(val) setValue(v) diff --git a/app/frontend/Components/Form/Inputs/PasswordInput.tsx b/app/frontend/Components/Form/Inputs/PasswordInput.tsx index 84192b1..3030c11 100644 --- a/app/frontend/Components/Form/Inputs/PasswordInput.tsx +++ b/app/frontend/Components/Form/Inputs/PasswordInput.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import PasswordInput, { type PasswordInputProps } from '@/Components/Inputs/PasswordInput' -import Field from '../Components/Field' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import PasswordInput, { type PasswordInputProps } from "@/Components/Inputs/PasswordInput" +import Field from "../Components/Field" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormPasswordInputProps extends diff --git a/app/frontend/Components/Form/Inputs/Radio/Group.tsx b/app/frontend/Components/Form/Inputs/Radio/Group.tsx index 9ca72cf..7544006 100644 --- a/app/frontend/Components/Form/Inputs/Radio/Group.tsx +++ b/app/frontend/Components/Form/Inputs/Radio/Group.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { useInertiaInput } from 'use-inertia-form' -import { Radio } from '@/Components/Inputs' -import { type RadioGroupProps } from '@mantine/core' +import React from "react" +import { useInertiaInput } from "use-inertia-form" +import { Radio } from "@/Components/Inputs" +import { type RadioGroupProps } from "@mantine/core" export interface FormRadioGroupProps extends RadioGroupProps { name: string diff --git a/app/frontend/Components/Form/Inputs/Radio/Radio.tsx b/app/frontend/Components/Form/Inputs/Radio/Radio.tsx index 1ab09a0..5ba893f 100644 --- a/app/frontend/Components/Form/Inputs/Radio/Radio.tsx +++ b/app/frontend/Components/Form/Inputs/Radio/Radio.tsx @@ -1,15 +1,15 @@ -import React from 'react' -import { useForm, useInertiaInput, type NestedObject } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { Field } from '@/Components/Form' -import RadioInput, { type RadioProps } from '@/Components/Inputs/Radio' -import FormRadioGroup from './Group' -import { type BaseFormInputProps } from '..' +import React from "react" +import { useForm, useInertiaInput, type NestedObject } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { Field } from "@/Components/Form" +import RadioInput, { type RadioProps } from "@/Components/Inputs/Radio" +import FormRadioGroup from "./Group" +import { type BaseFormInputProps } from ".." export interface FormRadioProps extends - Omit, - Omit, 'name'> {} + Omit, + Omit, "name"> {} const FormRadioComponent = ( { @@ -48,7 +48,7 @@ const FormRadioComponent = ( onChange={ handleChange } onBlur={ handleBlur } onFocus={ e => onFocus?.(value, form) } - style={ [{ padding: '14px 10px' }, style] } + style={ [{ padding: "14px 10px" }, style] } wrapper={ false } { ...props } /> diff --git a/app/frontend/Components/Form/Inputs/Radio/index.tsx b/app/frontend/Components/Form/Inputs/Radio/index.tsx index e8651d2..e3c0568 100644 --- a/app/frontend/Components/Form/Inputs/Radio/index.tsx +++ b/app/frontend/Components/Form/Inputs/Radio/index.tsx @@ -1,4 +1,4 @@ -export { Radio as GroupedRadio } from '@/Components/Inputs' +export { Radio as GroupedRadio } from "@/Components/Inputs" -import FormRadioInput from './Radio' +import FormRadioInput from "./Radio" export default FormRadioInput diff --git a/app/frontend/Components/Form/Inputs/RichText.tsx b/app/frontend/Components/Form/Inputs/RichText.tsx index 11b4e79..15d2c13 100644 --- a/app/frontend/Components/Form/Inputs/RichText.tsx +++ b/app/frontend/Components/Form/Inputs/RichText.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import Field from '../Components/Field' -import RichTextInput, { type RichTextInputProps } from '@/Components/Inputs/RichText' -import cx from 'clsx' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import Field from "../Components/Field" +import RichTextInput, { type RichTextInputProps } from "@/Components/Inputs/RichText" +import cx from "clsx" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormRichTextInputProps extends diff --git a/app/frontend/Components/Form/Inputs/SegmentedControl.tsx b/app/frontend/Components/Form/Inputs/SegmentedControl.tsx index 4cb618f..5924cac 100644 --- a/app/frontend/Components/Form/Inputs/SegmentedControl.tsx +++ b/app/frontend/Components/Form/Inputs/SegmentedControl.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import SegmentedControl, { type SegmentedControlProps } from '@/Components/Inputs/SegmentedControl' -import Field from '../Components/Field' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import SegmentedControl, { type SegmentedControlProps } from "@/Components/Inputs/SegmentedControl" +import Field from "../Components/Field" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormSegmentedControlProps extends diff --git a/app/frontend/Components/Form/Inputs/Select.tsx b/app/frontend/Components/Form/Inputs/Select.tsx index b57a1f0..084f452 100644 --- a/app/frontend/Components/Form/Inputs/Select.tsx +++ b/app/frontend/Components/Form/Inputs/Select.tsx @@ -1,28 +1,28 @@ -import React from 'react' -import { useInertiaInput, type UseFormProps, NestedObject } from 'use-inertia-form' -import Field from '../Components/Field' -import { type ComboboxData, type ComboboxItem, type ComboboxItemGroup } from '@mantine/core' -import { exclude } from '@/lib' -import { ConditionalWrapper, Group } from '@/Components' -import SelectInput, { type SelectInputProps } from '@/Components/Inputs/Select' -import { type BaseFormInputProps } from '.' +import React from "react" +import { useInertiaInput, type UseFormProps, NestedObject } from "use-inertia-form" +import Field from "../Components/Field" +import { type ComboboxData, type ComboboxItem, type ComboboxItemGroup } from "@mantine/core" +import { exclude } from "@/lib" +import { ConditionalWrapper, Group } from "@/Components" +import SelectInput, { type SelectInputProps } from "@/Components/Inputs/Select" +import { type BaseFormInputProps } from "." export type SelectOption = string | ComboboxItem | ComboboxItemGroup export { type ComboboxData, ComboboxItem, ComboboxItemGroup } -type OmittedOverwrittenTypes = 'onFocus'|'onBlur'|'onChange'|'onClear'|'onDropdownOpen'|'onDropdownClose'|'onOptionSubmit' +type OmittedOverwrittenTypes = "onFocus" | "onBlur" | "onChange" | "onClear" | "onDropdownOpen" | "onDropdownClose" | "onOptionSubmit" export interface FormSelectProps extends - Omit, + Omit, Omit, OmittedOverwrittenTypes> { - onChange?: (option: SelectOption|null, options: ComboboxData, form: UseFormProps) => void - onBlur?: (option: SelectOption|null, options: ComboboxData, form: UseFormProps) => void - onFocus?: (option: SelectOption|null, options: ComboboxData, form: UseFormProps) => void + onChange?: (option: SelectOption | null, options: ComboboxData, form: UseFormProps) => void + onBlur?: (option: SelectOption | null, options: ComboboxData, form: UseFormProps) => void + onFocus?: (option: SelectOption | null, options: ComboboxData, form: UseFormProps) => void onClear?: (options: ComboboxData, form: UseFormProps) => void onDropdownOpen?: (options: ComboboxData, form: UseFormProps) => void onDropdownClose?: (options: ComboboxData, form: UseFormProps) => void - onOptionSubmit?: (option: SelectOption|null, options: ComboboxData, form: UseFormProps) => void + onOptionSubmit?: (option: SelectOption | null, options: ComboboxData, form: UseFormProps) => void endpoint?: string newForm?: React.ReactElement field?: boolean @@ -62,8 +62,8 @@ const Select = ( clearErrorsOnChange, }) - const handleChange = (option: string|null) => { - setValue(option ? option : '') + const handleChange = (option: string | null) => { + setValue(option ? option : "") onChange?.(option, options || [], form) } @@ -133,7 +133,7 @@ const Select = ( error={ error } options={ options } wrapper={ false } - { ...exclude(props, 'value') } + { ...exclude(props, "value") } /> diff --git a/app/frontend/Components/Form/Inputs/SwatchInput.tsx b/app/frontend/Components/Form/Inputs/SwatchInput.tsx index e4ab779..91c920c 100644 --- a/app/frontend/Components/Form/Inputs/SwatchInput.tsx +++ b/app/frontend/Components/Form/Inputs/SwatchInput.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import SwatchInput, { type SwatchInputProps } from '@/Components/Inputs/SwatchInput' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import Field from '../Components/Field' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import SwatchInput, { type SwatchInputProps } from "@/Components/Inputs/SwatchInput" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import Field from "../Components/Field" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormSwatchInputProps extends diff --git a/app/frontend/Components/Form/Inputs/Switch.tsx b/app/frontend/Components/Form/Inputs/Switch.tsx index 3bf087e..2e0517b 100644 --- a/app/frontend/Components/Form/Inputs/Switch.tsx +++ b/app/frontend/Components/Form/Inputs/Switch.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import Field from '../Components/Field' -import SwitchInput, { type SwitchProps } from '@/Components/Inputs/Switch' -import { NestedObject, useInertiaInput } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import Field from "../Components/Field" +import SwitchInput, { type SwitchProps } from "@/Components/Inputs/Switch" +import { NestedObject, useInertiaInput } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormSwitchProps extends diff --git a/app/frontend/Components/Form/Inputs/TextInput.tsx b/app/frontend/Components/Form/Inputs/TextInput.tsx index 6a4bb57..abfdba7 100644 --- a/app/frontend/Components/Form/Inputs/TextInput.tsx +++ b/app/frontend/Components/Form/Inputs/TextInput.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import TextInput, { type TextInputProps } from '@/Components/Inputs/TextInput' -import Field from '../Components/Field' -import { useInertiaInput, type NestedObject } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { type InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import TextInput, { type TextInputProps } from "@/Components/Inputs/TextInput" +import Field from "../Components/Field" +import { useInertiaInput, type NestedObject } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { type InputConflicts, type BaseFormInputProps } from "." interface FormTextInputProps extends diff --git a/app/frontend/Components/Form/Inputs/Textarea.tsx b/app/frontend/Components/Form/Inputs/Textarea.tsx index 6e5d3dc..d82aa9f 100644 --- a/app/frontend/Components/Form/Inputs/Textarea.tsx +++ b/app/frontend/Components/Form/Inputs/Textarea.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import Field from '../Components/Field' -import TextareaInput, { type TextareaProps } from '@/Components/Inputs/Textarea' -import cx from 'clsx' -import { useInertiaInput, type NestedObject } from 'use-inertia-form' -import ConditionalWrapper from '@/Components/ConditionalWrapper' -import { InputConflicts, type BaseFormInputProps } from '.' +import React from "react" +import Field from "../Components/Field" +import TextareaInput, { type TextareaProps } from "@/Components/Inputs/Textarea" +import cx from "clsx" +import { useInertiaInput, type NestedObject } from "use-inertia-form" +import ConditionalWrapper from "@/Components/ConditionalWrapper" +import { InputConflicts, type BaseFormInputProps } from "." interface FormTextareaProps extends diff --git a/app/frontend/Components/Form/Inputs/index.ts b/app/frontend/Components/Form/Inputs/index.ts index a9aa069..1a99c6c 100644 --- a/app/frontend/Components/Form/Inputs/index.ts +++ b/app/frontend/Components/Form/Inputs/index.ts @@ -1,28 +1,28 @@ -import { NestedObject, UseFormProps, UseInertiaInputProps } from 'use-inertia-form' +import { NestedObject, UseFormProps, UseInertiaInputProps } from "use-inertia-form" -export { default as Autocomplete } from './Autocomplete' -export { default as CurrencyInput } from './CurrencyInput' -export { default as DateInput } from './DateInput' -export { default as DateTimeInput } from './DateTimeInput' -export { default as HiddenInput } from './HiddenInput' -export { default as MultiSelect } from './MultiSelect' -export { default as NumberInput } from './NumberInput' -export { default as PasswordInput } from './PasswordInput' -export { default as Radio } from './Radio' -export { default as RichText } from './RichText' -export { default as SegmentedControl } from './SegmentedControl' -export { default as Select } from './Select' -export { default as SwatchInput } from './SwatchInput' -export { default as Switch } from './Switch' -export { default as TextInput } from './TextInput' -export { default as Textarea } from './Textarea' +export { default as Autocomplete } from "./Autocomplete" +export { default as CurrencyInput } from "./CurrencyInput" +export { default as DateInput } from "./DateInput" +export { default as DateTimeInput } from "./DateTimeInput" +export { default as HiddenInput } from "./HiddenInput" +export { default as MultiSelect } from "./MultiSelect" +export { default as NumberInput } from "./NumberInput" +export { default as PasswordInput } from "./PasswordInput" +export { default as Radio } from "./Radio" +export { default as RichText } from "./RichText" +export { default as SegmentedControl } from "./SegmentedControl" +export { default as Select } from "./Select" +export { default as SwatchInput } from "./SwatchInput" +export { default as Switch } from "./Switch" +export { default as TextInput } from "./TextInput" +export { default as Textarea } from "./Textarea" export { default as Checkbox, GroupedCheckbox, -} from './Checkbox' +} from "./Checkbox" -export type InputConflicts = 'name' | 'onChange' | 'onBlur' | 'onFocus' | 'value' | 'defaultValue' +export type InputConflicts = "name" | "onChange" | "onBlur" | "onFocus" | "value" | "defaultValue" export interface BaseFormInputProps extends UseInertiaInputProps { model?: string diff --git a/app/frontend/Components/Form/index.ts b/app/frontend/Components/Form/index.ts index a7e944d..df570af 100644 --- a/app/frontend/Components/Form/index.ts +++ b/app/frontend/Components/Form/index.ts @@ -1,4 +1,4 @@ -export { default as Form, type FormProps } from './Form' +export { default as Form, type FormProps } from "./Form" -export * from './Components' -export * from './Inputs' +export * from "./Components" +export * from "./Inputs" diff --git a/app/frontend/Components/Icons/index.tsx b/app/frontend/Components/Icons/index.tsx index 818bead..0621095 100644 --- a/app/frontend/Components/Icons/index.tsx +++ b/app/frontend/Components/Icons/index.tsx @@ -14,7 +14,7 @@ export { TbSquarePlus as ReplenishIcon, TbSun as SunIcon, TbMoonStars as MoonIcon, -} from 'react-icons/tb' +} from "react-icons/tb" export { MdExitToApp as LogoutIcon, @@ -28,63 +28,63 @@ export { MdOutlineDelete as TrashIcon, MdCalendarMonth as CalendarIcon, // MdDragHandle as DraggableIcon, -} from 'react-icons/md' +} from "react-icons/md" export { AiOutlinePlusCircle as PlusCircleIcon, AiOutlineMinusCircle as MinusCircleIcon, -} from 'react-icons/ai' +} from "react-icons/ai" export { BiLinkExternal as ExternalLinkIcon, -} from 'react-icons/bi' +} from "react-icons/bi" export { -} from 'react-icons/io5' +} from "react-icons/io5" export { BsCheck2Circle as CheckIcon, -} from 'react-icons/bs' +} from "react-icons/bs" export { FaHome as HomeIcon, -} from 'react-icons/fa' +} from "react-icons/fa" export { RiDraggable as DraggableIcon, -} from 'react-icons/ri' +} from "react-icons/ri" export { RxClock as ClockIcon, -} from 'react-icons/rx' +} from "react-icons/rx" export { -} from 'react-icons/ti' +} from "react-icons/ti" export { GrFormNext as NextIcon, GrFormPrevious as PreviousIcon, GrMoney as CoinsIcon, -} from 'react-icons/gr' +} from "react-icons/gr" export { TbChevronsDown as DoubleDownArrowIcon, TbChevronDown as DownArrowIcon, -} from 'react-icons/tb' +} from "react-icons/tb" export { HiOutlineUserGroup as UserGroupIcon, -} from 'react-icons/hi' +} from "react-icons/hi" export { -} from 'react-icons/cg' +} from "react-icons/cg" export { VscOrganization as MembersIcon, -} from 'react-icons/vsc' +} from "react-icons/vsc" export { LiaHandsHelpingSolid as HelpingIcon, -} from 'react-icons/lia' +} from "react-icons/lia" diff --git a/app/frontend/Components/Inputs/AutocompleteInput.tsx b/app/frontend/Components/Inputs/AutocompleteInput.tsx index bd6b22d..9a2e745 100644 --- a/app/frontend/Components/Inputs/AutocompleteInput.tsx +++ b/app/frontend/Components/Inputs/AutocompleteInput.tsx @@ -1,7 +1,7 @@ -import React, { forwardRef } from 'react' -import { Autocomplete, type AutocompleteProps as MantineAutocompleteProps } from '@mantine/core' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { Autocomplete, type AutocompleteProps as MantineAutocompleteProps } from "@mantine/core" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" export interface AutocompleteProps extends MantineAutocompleteProps, BaseInputProps {} @@ -17,7 +17,7 @@ const AutocompleteComponent = forwardRef(( ref={ ref } id={ inputId } name={ name } - style={ [{ padding: '14px 10px' }, style] } + style={ [{ padding: "14px 10px" }, style] } wrapperProps={ wrapper ? {} : wrapperProps } onClick={ e => { e.stopPropagation() diff --git a/app/frontend/Components/Inputs/Checkbox.tsx b/app/frontend/Components/Inputs/Checkbox.tsx index 479f08e..1409449 100644 --- a/app/frontend/Components/Inputs/Checkbox.tsx +++ b/app/frontend/Components/Inputs/Checkbox.tsx @@ -1,7 +1,7 @@ -import React, { forwardRef } from 'react' -import { Checkbox, type CheckboxProps as MantineCheckboxProps } from '@mantine/core' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { Checkbox, type CheckboxProps as MantineCheckboxProps } from "@mantine/core" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" export interface CheckboxProps extends MantineCheckboxProps, BaseInputProps {} diff --git a/app/frontend/Components/Inputs/CurrencyInput.tsx b/app/frontend/Components/Inputs/CurrencyInput.tsx index c56f3f9..7378079 100644 --- a/app/frontend/Components/Inputs/CurrencyInput.tsx +++ b/app/frontend/Components/Inputs/CurrencyInput.tsx @@ -1,15 +1,14 @@ -import React, { forwardRef } from 'react' -import { NumberInput, type NumberInputProps } from '@mantine/core' -import Label from './Label' -import InputWrapper from './InputWrapper' -import { type BaseInputProps } from '.' +import React, { forwardRef } from "react" +import { NumberInput, type NumberInputProps } from "@mantine/core" +import Label from "./Label" +import InputWrapper from "./InputWrapper" +import { type BaseInputProps } from "." export interface CurrencyInputProps extends NumberInputProps, - BaseInputProps -{ - symbol?: string|React.ReactNode + BaseInputProps { + symbol?: string | React.ReactNode } const NumberInputComponent = forwardRef(( @@ -19,8 +18,8 @@ const NumberInputComponent = forwardRef(( required = false, id, pattern, - size = 'md', - symbol = '$', + size = "md", + symbol = "$", wrapper, wrapperProps, onClick, diff --git a/app/frontend/Components/Inputs/DateInput.tsx b/app/frontend/Components/Inputs/DateInput.tsx index 3abdc2b..0a0f3d6 100644 --- a/app/frontend/Components/Inputs/DateInput.tsx +++ b/app/frontend/Components/Inputs/DateInput.tsx @@ -1,16 +1,15 @@ -import React, { useEffect, useState, forwardRef } from 'react' -import Label from './Label' -import { DatePickerInput, type DatePickerInputProps } from '@mantine/dates' -import { CalendarIcon } from '@/Components/Icons' -import { type DateInputValue, type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' -import { isUnset } from '@/lib' +import React, { useEffect, useState, forwardRef } from "react" +import Label from "./Label" +import { DatePickerInput, type DatePickerInputProps } from "@mantine/dates" +import { CalendarIcon } from "@/Components/Icons" +import { type DateInputValue, type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" +import { isUnset } from "@/lib" export interface DateInputProps extends - Omit, - BaseInputProps -{ + Omit, + BaseInputProps { name?: string id?: string value: DateInputValue @@ -23,10 +22,10 @@ const DateInputComponent = forwardRef(( label, id, name, - type = 'default', - size = 'md', - radius = 'xs', - valueFormat = 'L', + type = "default", + size = "md", + radius = "xs", + valueFormat = "L", required, wrapper, wrapperProps, @@ -53,7 +52,7 @@ const DateInputComponent = forwardRef(( if(datePickerType === type) return // DatesRangeValue and Date[] are the Array type options - if(type === 'range') { + if(type === "range") { if(Array.isArray(localValue)) { // An array of length 2 indicates it's already a range of dates if(localValue.length !== 2) { diff --git a/app/frontend/Components/Inputs/DateTimeInput.tsx b/app/frontend/Components/Inputs/DateTimeInput.tsx index b2be1ac..69459f9 100644 --- a/app/frontend/Components/Inputs/DateTimeInput.tsx +++ b/app/frontend/Components/Inputs/DateTimeInput.tsx @@ -1,10 +1,10 @@ -import React, { forwardRef } from 'react' -import Label from './Label' -import { DateTimePicker, DateTimePickerProps } from '@mantine/dates' -import { CalendarIcon } from '../Icons' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' -import { isUnset } from '@/lib' +import React, { forwardRef } from "react" +import Label from "./Label" +import { DateTimePicker, DateTimePickerProps } from "@mantine/dates" +import { CalendarIcon } from "../Icons" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" +import { isUnset } from "@/lib" export interface DateTimeProps extends DateTimePickerProps, BaseInputProps { name?: string @@ -21,9 +21,9 @@ const DateTime = forwardRef(( name, required, value, - size = 'md', - radius = 'xs', - valueFormat = 'L LT', + size = "md", + radius = "xs", + valueFormat = "L LT", wrapper, wrapperProps, onClick, diff --git a/app/frontend/Components/Inputs/HiddenInput.tsx b/app/frontend/Components/Inputs/HiddenInput.tsx index 2eea7b1..b9d4f5d 100644 --- a/app/frontend/Components/Inputs/HiddenInput.tsx +++ b/app/frontend/Components/Inputs/HiddenInput.tsx @@ -1,5 +1,5 @@ -import React, { forwardRef } from 'react' -import { InputProps } from 'react-html-props' +import React, { forwardRef } from "react" +import { InputProps } from "react-html-props" const TextInputComponent = forwardRef(( { name, id, ...props }, diff --git a/app/frontend/Components/Inputs/InputWrapper.tsx b/app/frontend/Components/Inputs/InputWrapper.tsx index b8b7896..8106d21 100644 --- a/app/frontend/Components/Inputs/InputWrapper.tsx +++ b/app/frontend/Components/Inputs/InputWrapper.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import ConditionalWrapper from '../ConditionalWrapper' -import { Box } from '@/Components' +import React from "react" +import ConditionalWrapper from "../ConditionalWrapper" +import { Box } from "@/Components" interface InputWrapper { children: React.ReactNode diff --git a/app/frontend/Components/Inputs/Label.tsx b/app/frontend/Components/Inputs/Label.tsx index aa586ab..d97d6c8 100644 --- a/app/frontend/Components/Inputs/Label.tsx +++ b/app/frontend/Components/Inputs/Label.tsx @@ -1,11 +1,10 @@ -import React from 'react' -import { Box, type BoxProps } from '@mantine/core' -import cx from 'clsx' +import React from "react" +import { Box, type BoxProps } from "@mantine/core" +import cx from "clsx" interface LabelProps extends BoxProps, - Omit, keyof BoxProps> -{ + Omit, keyof BoxProps> { required?: boolean } diff --git a/app/frontend/Components/Inputs/MultiSelect.tsx b/app/frontend/Components/Inputs/MultiSelect.tsx index cecb2dd..29908d1 100644 --- a/app/frontend/Components/Inputs/MultiSelect.tsx +++ b/app/frontend/Components/Inputs/MultiSelect.tsx @@ -1,12 +1,12 @@ -import React, { forwardRef } from 'react' -import { MultiSelect, type ComboboxData, type MultiSelectProps as MantineMultiSelectInputProps } from '@mantine/core' -import Label from './Label' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' -import { router } from '@inertiajs/react' -import { coerceArray } from '@/lib' +import React, { forwardRef } from "react" +import { MultiSelect, type ComboboxData, type MultiSelectProps as MantineMultiSelectInputProps } from "@mantine/core" +import Label from "./Label" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" +import { router } from "@inertiajs/react" +import { coerceArray } from "@/lib" -export interface MultiSelectInputProps extends Omit, BaseInputProps { +export interface MultiSelectInputProps extends Omit, BaseInputProps { options?: ComboboxData fetchOnOpen?: string } @@ -18,7 +18,7 @@ const MultiSelectComponent = forwardRef required, id, name, - size = 'md', + size = "md", maxDropdownHeight = 400, wrapper, wrapperProps, diff --git a/app/frontend/Components/Inputs/NumberInput.tsx b/app/frontend/Components/Inputs/NumberInput.tsx index d790ea1..b86f197 100644 --- a/app/frontend/Components/Inputs/NumberInput.tsx +++ b/app/frontend/Components/Inputs/NumberInput.tsx @@ -1,13 +1,13 @@ -import React, { forwardRef } from 'react' -import { NumberInput, type NumberInputProps as MantineNumberInputProps } from '@mantine/core' -import Label from './Label' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { NumberInput, type NumberInputProps as MantineNumberInputProps } from "@mantine/core" +import Label from "./Label" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" export interface NumberInputProps extends MantineNumberInputProps, BaseInputProps {} const NumberInputComponent = forwardRef(( - { label, name, required = false, value, id, size = 'md', wrapper, wrapperProps, onClick, ...props }, + { label, name, required = false, value, id, size = "md", wrapper, wrapperProps, onClick, ...props }, ref, ) => { const inputId = id || name diff --git a/app/frontend/Components/Inputs/PasswordInput.tsx b/app/frontend/Components/Inputs/PasswordInput.tsx index 2718ca2..47a1e4c 100644 --- a/app/frontend/Components/Inputs/PasswordInput.tsx +++ b/app/frontend/Components/Inputs/PasswordInput.tsx @@ -1,13 +1,13 @@ -import React, { forwardRef } from 'react' -import { PasswordInput, type PasswordInputProps as MantinePasswordInputProps } from '@mantine/core' -import Label from './Label' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { PasswordInput, type PasswordInputProps as MantinePasswordInputProps } from "@mantine/core" +import Label from "./Label" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" export interface PasswordInputProps extends MantinePasswordInputProps, BaseInputProps {} const PasswordInputComponent = forwardRef(( - { label, name, required = false, id, size = 'md', wrapper, wrapperProps, onClick, ...props }, + { label, name, required = false, id, size = "md", wrapper, wrapperProps, onClick, ...props }, ref, ) => { const inputId = id || name diff --git a/app/frontend/Components/Inputs/Radio.tsx b/app/frontend/Components/Inputs/Radio.tsx index 4146a10..6f07fa5 100644 --- a/app/frontend/Components/Inputs/Radio.tsx +++ b/app/frontend/Components/Inputs/Radio.tsx @@ -1,9 +1,9 @@ -import React, { forwardRef } from 'react' -import { Radio, type RadioProps as MantineRadioProps } from '@mantine/core' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { Radio, type RadioProps as MantineRadioProps } from "@mantine/core" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" -export interface RadioProps extends Omit, BaseInputProps { +export interface RadioProps extends Omit, BaseInputProps { value: string } diff --git a/app/frontend/Components/Inputs/RichText.tsx b/app/frontend/Components/Inputs/RichText.tsx index 03492b4..bbf348b 100644 --- a/app/frontend/Components/Inputs/RichText.tsx +++ b/app/frontend/Components/Inputs/RichText.tsx @@ -1,8 +1,8 @@ -import React, { forwardRef } from 'react' -import RichTextEditor, { type RichTextEditorProps } from '../RichTextEditor' -import Label from './Label' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import RichTextEditor, { type RichTextEditorProps } from "../RichTextEditor" +import Label from "./Label" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" export interface RichTextInputProps extends RichTextEditorProps, BaseInputProps { label?: React.ReactNode diff --git a/app/frontend/Components/Inputs/SegmentedControl.tsx b/app/frontend/Components/Inputs/SegmentedControl.tsx index 223ab81..b8bd80e 100644 --- a/app/frontend/Components/Inputs/SegmentedControl.tsx +++ b/app/frontend/Components/Inputs/SegmentedControl.tsx @@ -1,17 +1,17 @@ -import React, { forwardRef } from 'react' +import React, { forwardRef } from "react" import { SegmentedControl, useMantineTheme, type SegmentedControlProps as MantineSegmentedControlProps, type SegmentedControlItem, -} from '@mantine/core' -import Label from './Label' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +} from "@mantine/core" +import Label from "./Label" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" -export interface SegmentedControlProps extends Omit, BaseInputProps { +export interface SegmentedControlProps extends Omit, BaseInputProps { label?: string - labelPosition?: 'start'|'end' + labelPosition?: "start" | "end" name: string options: SegmentedControlItem[] id?: string @@ -21,7 +21,7 @@ export interface SegmentedControlProps extends Omit(( { label, - labelPosition = 'start', + labelPosition = "start", options, name, id, @@ -40,7 +40,7 @@ const SegmentedControlComponent = forwardRef - { label && labelPosition === 'start' && } + { label && labelPosition === "start" && } - { label && labelPosition === 'end' && } + { label && labelPosition === "end" && } ) }) diff --git a/app/frontend/Components/Inputs/Select.tsx b/app/frontend/Components/Inputs/Select.tsx index 78189c8..07d28cd 100644 --- a/app/frontend/Components/Inputs/Select.tsx +++ b/app/frontend/Components/Inputs/Select.tsx @@ -1,12 +1,12 @@ -import React, { forwardRef } from 'react' -import { Select, type ComboboxData, type SelectProps } from '@mantine/core' -import { router } from '@inertiajs/react' -import { coerceArray } from '@/lib' -import { type BaseInputProps } from '.' -import Label from './Label' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { Select, type ComboboxData, type SelectProps } from "@mantine/core" +import { router } from "@inertiajs/react" +import { coerceArray } from "@/lib" +import { type BaseInputProps } from "." +import Label from "./Label" +import InputWrapper from "./InputWrapper" -export interface SelectInputProps extends Omit, BaseInputProps { +export interface SelectInputProps extends Omit, BaseInputProps { options?: ComboboxData fetchOnOpen?: string } @@ -18,7 +18,7 @@ const SelectComponent = forwardRef(( required, id, name, - size = 'md', + size = "md", maxDropdownHeight = 400, fetchOnOpen, onDropdownOpen, diff --git a/app/frontend/Components/Inputs/SwatchInput.tsx b/app/frontend/Components/Inputs/SwatchInput.tsx index 8a7e077..e86f53a 100644 --- a/app/frontend/Components/Inputs/SwatchInput.tsx +++ b/app/frontend/Components/Inputs/SwatchInput.tsx @@ -1,12 +1,12 @@ -import React, { forwardRef, useState } from 'react' -import HiddenInput from './HiddenInput' -import SwatchPicker from '../SwatchPicker' -import Label from './Label' -import { InputProps } from 'react-html-props' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef, useState } from "react" +import HiddenInput from "./HiddenInput" +import SwatchPicker from "../SwatchPicker" +import Label from "./Label" +import { InputProps } from "react-html-props" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" -export interface SwatchInputProps extends Omit, BaseInputProps { +export interface SwatchInputProps extends Omit, BaseInputProps { label?: React.ReactNode initialValue?: string onChange?: (color: string) => void @@ -17,7 +17,7 @@ const SwatchInput = forwardRef(( { label, id, name, required, initialValue, onChange, wrapper, wrapperProps, ...props }, ref, ) => { - const [color, setColor] = useState(initialValue || '') + const [color, setColor] = useState(initialValue || "") const handleChange = (color: string) => { setColor(color) diff --git a/app/frontend/Components/Inputs/Switch.tsx b/app/frontend/Components/Inputs/Switch.tsx index 6c0c0ce..f104384 100644 --- a/app/frontend/Components/Inputs/Switch.tsx +++ b/app/frontend/Components/Inputs/Switch.tsx @@ -1,7 +1,7 @@ -import React, { forwardRef } from 'react' -import { Switch, type SwitchProps as MantineSwitchProps } from '@mantine/core' -import { type BaseInputProps } from '.' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { Switch, type SwitchProps as MantineSwitchProps } from "@mantine/core" +import { type BaseInputProps } from "." +import InputWrapper from "./InputWrapper" export interface SwitchProps extends MantineSwitchProps, BaseInputProps {} @@ -18,7 +18,7 @@ const SwitchComponent = forwardRef(( id={ inputId } name={ name } required={ props.required } - style={ [{ padding: '14px 10px' }, style] } + style={ [{ padding: "14px 10px" }, style] } onClick={ e => { e.stopPropagation() onClick?.(e) diff --git a/app/frontend/Components/Inputs/TextInput.tsx b/app/frontend/Components/Inputs/TextInput.tsx index 3d7ceb5..a6afe10 100644 --- a/app/frontend/Components/Inputs/TextInput.tsx +++ b/app/frontend/Components/Inputs/TextInput.tsx @@ -1,10 +1,10 @@ -import React, { forwardRef, useState } from 'react' -import { TextInput, type TextInputProps as MantineTextInputProps } from '@mantine/core' -import { type BaseInputProps } from '.' -import Label from './Label' -import InputWrapper from './InputWrapper' -import { CrossIcon } from '../Icons' -import { isUnset } from '@/lib' +import React, { forwardRef, useState } from "react" +import { TextInput, type TextInputProps as MantineTextInputProps } from "@mantine/core" +import { type BaseInputProps } from "." +import Label from "./Label" +import InputWrapper from "./InputWrapper" +import { CrossIcon } from "../Icons" +import { isUnset } from "@/lib" export interface TextInputProps extends MantineTextInputProps, BaseInputProps { clearable?: boolean @@ -16,7 +16,7 @@ const TextInputComponent = forwardRef(( label, required = false, id, - size = 'md', + size = "md", wrapper, wrapperProps, clearable = false, @@ -29,7 +29,7 @@ const TextInputComponent = forwardRef(( ref, ) => { // Manage value as a local state to enable clearable feature - const [localValue, setLocalValue] = useState(value || '') + const [localValue, setLocalValue] = useState(value || "") const handleChange = (e: React.ChangeEvent) => { if(onChange) { @@ -42,7 +42,7 @@ const TextInputComponent = forwardRef(( const handleClear = () => { const fakeEvent = { target: { - value: '', + value: "", }, } as React.ChangeEvent handleChange(fakeEvent) diff --git a/app/frontend/Components/Inputs/Textarea.tsx b/app/frontend/Components/Inputs/Textarea.tsx index 6bd0c61..cb672ef 100644 --- a/app/frontend/Components/Inputs/Textarea.tsx +++ b/app/frontend/Components/Inputs/Textarea.tsx @@ -1,8 +1,8 @@ -import React, { forwardRef } from 'react' -import { Textarea, type TextareaProps as MantineTextareaProps } from '@mantine/core' -import { type BaseInputProps } from '.' -import Label from './Label' -import InputWrapper from './InputWrapper' +import React, { forwardRef } from "react" +import { Textarea, type TextareaProps as MantineTextareaProps } from "@mantine/core" +import { type BaseInputProps } from "." +import Label from "./Label" +import InputWrapper from "./InputWrapper" export interface TextareaProps extends MantineTextareaProps, BaseInputProps {} diff --git a/app/frontend/Components/Inputs/index.ts b/app/frontend/Components/Inputs/index.ts index 11fd3cd..4df3466 100644 --- a/app/frontend/Components/Inputs/index.ts +++ b/app/frontend/Components/Inputs/index.ts @@ -1,21 +1,21 @@ -import { type DateValue, type DatesRangeValue } from '@mantine/dates' +import { type DateValue, type DatesRangeValue } from "@mantine/dates" -export { default as AutocompleteInput } from './AutocompleteInput' -export { default as Checkbox } from './Checkbox' -export { default as CurrencyInput } from './CurrencyInput' -export { default as DateInput } from './DateInput' -export { default as DateTimeInput } from './DateTimeInput' -export { default as HiddenInput } from './HiddenInput' -export { default as MultiSelect } from './MultiSelect' -export { default as NumberInput } from './NumberInput' -export { default as PasswordInput } from './PasswordInput' -export { default as Radio } from './Radio' -export { default as RichText } from './RichText' -export { default as SegmentedControl } from './SegmentedControl' -export { default as Select } from './Select' -export { default as SwatchInput } from './SwatchInput' -export { default as Textarea } from './Textarea' -export { default as TextInput } from './TextInput' +export { default as AutocompleteInput } from "./AutocompleteInput" +export { default as Checkbox } from "./Checkbox" +export { default as CurrencyInput } from "./CurrencyInput" +export { default as DateInput } from "./DateInput" +export { default as DateTimeInput } from "./DateTimeInput" +export { default as HiddenInput } from "./HiddenInput" +export { default as MultiSelect } from "./MultiSelect" +export { default as NumberInput } from "./NumberInput" +export { default as PasswordInput } from "./PasswordInput" +export { default as Radio } from "./Radio" +export { default as RichText } from "./RichText" +export { default as SegmentedControl } from "./SegmentedControl" +export { default as Select } from "./Select" +export { default as SwatchInput } from "./SwatchInput" +export { default as Textarea } from "./Textarea" +export { default as TextInput } from "./TextInput" export interface BaseInputProps { wrapper?: boolean diff --git a/app/frontend/Components/Label/index.tsx b/app/frontend/Components/Label/index.tsx index c013439..4f27864 100644 --- a/app/frontend/Components/Label/index.tsx +++ b/app/frontend/Components/Label/index.tsx @@ -1,11 +1,10 @@ -import React from 'react' -import { Box, type BoxProps } from '@mantine/core' -import cx from 'clsx' +import React from "react" +import { Box, type BoxProps } from "@mantine/core" +import cx from "clsx" interface LabelProps extends BoxProps, - Omit, keyof BoxProps> -{ + Omit, keyof BoxProps> { required?: boolean } diff --git a/app/frontend/Components/Link/AnchorLink.tsx b/app/frontend/Components/Link/AnchorLink.tsx index b4d1fae..18bb5ef 100644 --- a/app/frontend/Components/Link/AnchorLink.tsx +++ b/app/frontend/Components/Link/AnchorLink.tsx @@ -1,10 +1,10 @@ -import React, { forwardRef } from 'react' -import { Link, type InertiaLinkProps } from '@inertiajs/react' -import { Anchor, type AnchorProps } from '@mantine/core' +import React, { forwardRef } from "react" +import { Link, type InertiaLinkProps } from "@inertiajs/react" +import { Anchor, type AnchorProps } from "@mantine/core" export interface AnchorLinkProps - extends Omit, - Omit { + extends Omit, + Omit { } diff --git a/app/frontend/Components/Link/ExternalLink.tsx b/app/frontend/Components/Link/ExternalLink.tsx index f6bcfb7..99b9dad 100644 --- a/app/frontend/Components/Link/ExternalLink.tsx +++ b/app/frontend/Components/Link/ExternalLink.tsx @@ -1,15 +1,15 @@ -import React, { forwardRef } from 'react' -import normalizeUrl from 'normalize-url' -import { ExternalLinkIcon } from '@/Components/Icons' -import { Anchor, type AnchorProps } from '@mantine/core' -import * as classes from './Link.css' -import cx from 'clsx' +import React, { forwardRef } from "react" +import normalizeUrl from "normalize-url" +import { ExternalLinkIcon } from "@/Components/Icons" +import { Anchor, type AnchorProps } from "@mantine/core" +import * as classes from "./Link.css" +import cx from "clsx" interface ExternalLinkProps extends AnchorProps, - Omit, keyof AnchorProps> { + Omit, keyof AnchorProps> { href: string - as?: 'a'|'button' + as?: "a" | "button" disabled?: boolean } @@ -21,7 +21,7 @@ const ExternalLink = forwardRef(( return ( + visit?: Omit buttonProps?: ButtonProps disabled?: boolean } @@ -20,7 +20,7 @@ const InertiaLinkComponent = forwardRef(( { children, href, - as = 'a', + as = "a", method, visit, buttonProps, @@ -44,19 +44,19 @@ const InertiaLinkComponent = forwardRef(( const mergedButtonProps = Object.assign( { disabled }, - exclude(buttonProps, 'onClick'), - exclude(props, ['classNames', 'styles', 'vars', 'onClick']), + exclude(buttonProps, "onClick"), + exclude(props, ["classNames", "styles", "vars", "onClick"]), ) - const processedHref = disabled ? '#' : href + const processedHref = disabled ? "#" : href - if((method !== undefined && method !== 'get')) { + if((method !== undefined && method !== "get")) { return } - if(as === 'button') { + if(as === "button") { return
(( const { auth: { user: { table_preferences } } } = usePageProps() const { tableState: { model, columns } } = useTableContext() + const isColumnHidden = useCallback((columnIndex: number) => ( + columns[columnIndex]?.hideable && + model && + table_preferences?.[model]?.hide?.[columns[columnIndex].hideable] + ), [columns, model, table_preferences]) + const length = rows?.length || 0 return ( - { selectable && length > 0 && } + { selectable && length > 0 && } { children && React.Children.map(children, (cell, i) => { - if(( - columns[i]?.hideable && - model && - table_preferences?.[model]?.hide?.[columns[i].hideable] - )) { - return + const label = columns[i]?.label + + if(isColumnHidden(i)) { + return + } + + const cellProps = { + key: label, + "data-cell": label, + role: "cell", } - return React.cloneElement(cell, { - key: columns[i]?.label, - 'data-cell': columns[i]?.label, - role: 'cell', - }) + + if(Array.isArray(cell)) { + return cell.map(subCell => React.cloneElement(subCell, cellProps)) + } + + return React.cloneElement(cell, cellProps) }) } ) diff --git a/app/frontend/Components/Table/Row/HeadCheckbox.tsx b/app/frontend/Components/Table/Row/HeadCheckbox.tsx index 300ea8b..71259f0 100644 --- a/app/frontend/Components/Table/Row/HeadCheckbox.tsx +++ b/app/frontend/Components/Table/Row/HeadCheckbox.tsx @@ -1,8 +1,7 @@ -import React from 'react' -import Td from '../Td' -import { Checkbox } from '@/Components/Inputs' -import { CheckboxProps } from '@/Components/Inputs/Checkbox' -import { useTableContext } from '../TableContext' +import Td from "../Td" +import { Checkbox } from "@/Components/Inputs" +import { CheckboxProps } from "@/Components/Inputs/Checkbox" +import { useTableContext } from "../TableContext" interface RowCheckBoxProps extends CheckboxProps { selected: Set diff --git a/app/frontend/Components/Table/Row/HeadRow.tsx b/app/frontend/Components/Table/Row/HeadRow.tsx index 5943836..336fd26 100644 --- a/app/frontend/Components/Table/Row/HeadRow.tsx +++ b/app/frontend/Components/Table/Row/HeadRow.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, forwardRef } from 'react' -import { type TableRow } from './index' -import { Table } from '@mantine/core' -import HeadCheckbox from './HeadCheckbox' -import { useTableContext } from '../TableContext' -import { useCheckboxState } from '@/lib/hooks' -import { coerceArray } from '../../../lib/index' +import React, { useEffect, forwardRef } from "react" +import { type TableRow } from "./index" +import { Table } from "@mantine/core" +import HeadCheckbox from "./HeadCheckbox" +import { useTableContext } from "../TableContext" +import { useCheckboxState } from "@/lib/hooks" +import { coerceArray } from "../../../lib/index" interface HeadRowProps extends TableRow { name?: string diff --git a/app/frontend/Components/Table/Row/RowCheckbox.tsx b/app/frontend/Components/Table/Row/RowCheckbox.tsx index e8c5310..a080976 100644 --- a/app/frontend/Components/Table/Row/RowCheckbox.tsx +++ b/app/frontend/Components/Table/Row/RowCheckbox.tsx @@ -1,9 +1,8 @@ -import React from 'react' -import Td from '../Td' -import { Checkbox } from '@/Components/Inputs' -import { CheckboxProps } from '@/Components/Inputs/Checkbox' -import { useTableContext } from '../TableContext' -import cx from 'clsx' +import Td from "../Td" +import { Checkbox } from "@/Components/Inputs" +import { CheckboxProps } from "@/Components/Inputs/Checkbox" +import { useTableContext } from "../TableContext" +import cx from "clsx" interface RowCheckBox extends CheckboxProps { name: string @@ -25,7 +24,7 @@ const RowCheckbox = ({ name, selected, ...props }: RowCheckBox) => { } return ( - + ) diff --git a/app/frontend/Components/Table/Row/RowInContext.tsx b/app/frontend/Components/Table/Row/RowInContext.tsx index cab35b1..a36b3db 100644 --- a/app/frontend/Components/Table/Row/RowInContext.tsx +++ b/app/frontend/Components/Table/Row/RowInContext.tsx @@ -1,11 +1,11 @@ -import React, { forwardRef } from 'react' -import { useTableSectionContext } from '../TableContext' -import { type TableRow } from './index' +import React, { forwardRef } from "react" +import { useTableSectionContext } from "../TableContext" +import { type TableRow } from "./index" -import HeadRow from './HeadRow' -import BodyRow from './BodyRow' +import HeadRow from "./HeadRow" +import BodyRow from "./BodyRow" -interface RowInContextProps extends Omit { +interface RowInContextProps extends Omit { name?: string rows?: Record[] selectable: boolean @@ -18,7 +18,7 @@ const RowInContext = forwardRef(( ) => { const { section } = useTableSectionContext() - return section === 'head' ? + return section === "head" ? { children } : { children } diff --git a/app/frontend/Components/Table/Row/index.tsx b/app/frontend/Components/Table/Row/index.tsx index 12d6644..6f572ff 100644 --- a/app/frontend/Components/Table/Row/index.tsx +++ b/app/frontend/Components/Table/Row/index.tsx @@ -1,13 +1,12 @@ -import React from 'react' -import { useTableContext } from '../TableContext' -import RowInContext from './RowInContext' -import { Table, type TableTrProps } from '@mantine/core' +import { useTableContext } from "../TableContext" +import RowInContext from "./RowInContext" +import { Table, type TableTrProps } from "@mantine/core" export interface TableRow extends TableTrProps { - children?: JSX.Element | JSX.Element[] + children?: React.ReactElement | React.ReactElement[] | (React.ReactElement | React.ReactElement[])[] } -interface TableRowProps extends Omit { +interface TableRowProps extends Omit { render?: any name?: string } diff --git a/app/frontend/Components/Table/RowIterator.tsx b/app/frontend/Components/Table/RowIterator.tsx index 97ddf84..3f945d4 100644 --- a/app/frontend/Components/Table/RowIterator.tsx +++ b/app/frontend/Components/Table/RowIterator.tsx @@ -1,7 +1,8 @@ -import React from 'react' -import { useTableContext } from './TableContext' -import Table from '.' -import cx from 'clsx' +import React from "react" +import { useTableContext } from "./TableContext" +import Table from "." + +import cx from "clsx" const RowIterator = ({ render }: { render: (obj: any) => JSX.Element }) => { const { tableState: { selected, rows, columns, selectable } } = useTableContext() diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/AdvancedSearch.css.ts b/app/frontend/Components/Table/SearchInput/AdvancedSearch/AdvancedSearch.css.ts index 47c0876..6d82de1 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/AdvancedSearch.css.ts +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/AdvancedSearch.css.ts @@ -1,6 +1,6 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' -import { rem } from '@mantine/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" +import { rem } from "@mantine/core" export const paper = css` position: absolute; diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Date.tsx b/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/SearchDateInput.tsx similarity index 72% rename from app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Date.tsx rename to app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/SearchDateInput.tsx index ee85b5a..5f0b123 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Date.tsx +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/SearchDateInput.tsx @@ -1,8 +1,8 @@ -import React, { useEffect } from 'react' -import { DateInput, type DateInputValue } from '@/Components/Inputs' -import { type AdvancedInputProps } from '.' +import React, { useEffect } from "react" +import { DateInput, type DateInputValue } from "@/Components/Inputs" +import { type AdvancedInputProps } from "." -const Date = ({ +const SearchDateInput = ({ advancedSearch, name, }: AdvancedInputProps) => { @@ -25,7 +25,7 @@ const Date = ({ const type = values.get(`${name}[type]`) useEffect(() => { - if(type !== 'range') { + if(type !== "range") { setInputValue(`${name}[end]`, null) } }, [type, name, setInputValue]) @@ -35,9 +35,9 @@ const Date = ({ label="Date" { ...{ mb, wrapperProps, value } } onChange={ handleChange } - type={ type === 'range' ? 'range' : 'default' } + type={ type === "range" ? "range" : "default" } /> ) } -export default Date +export default SearchDateInput diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Type.tsx b/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Type.tsx index 6abe265..3a6d1e3 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Type.tsx +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/Type.tsx @@ -1,15 +1,14 @@ -import React from 'react' -import { Select } from '@/Components/Inputs' -import { type AdvancedInputProps } from '.' +import { Select } from "@/Components/Inputs" +import { type AdvancedInputProps } from "." export const dateRangeOptions = [ - { label: 'Exact Date', value: 'exact' }, - { label: 'Before', value: 'before' }, - { label: 'After', value: 'after' }, - { label: 'Between', value: 'range' }, + { label: "Exact Date", value: "exact" }, + { label: "Before", value: "before" }, + { label: "After", value: "after" }, + { label: "Between", value: "range" }, ] -type DateRangeType = typeof dateRangeOptions[number]['value'] +type DateRangeType = typeof dateRangeOptions[number]["value"] const Type = ({ advancedSearch, @@ -17,7 +16,7 @@ const Type = ({ }: AdvancedInputProps) => { const { inputProps, setInputValue } = advancedSearch - const handleChange = (value: DateRangeType|null) => { + const handleChange = (value: DateRangeType | null) => { if(!value) return setInputValue(`${name}[type]`, value) diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/index.ts b/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/index.ts index 13a5c6d..1c118cf 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/index.ts +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/DateRangeInputs/index.ts @@ -1,9 +1,9 @@ -import useAdvancedSearch from '../useAdvancedSearch' +import useAdvancedSearch from "../useAdvancedSearch" export interface AdvancedInputProps { advancedSearch: ReturnType name: string } -export { default as SearchDateTypeInput } from './Type' -export { default as SearchDateInput } from './Date' +export { default as SearchDateTypeInput } from "./Type" +export { default as SearchDateInput } from "./SearchDateInput" diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/buildSearchLink.ts b/app/frontend/Components/Table/SearchInput/AdvancedSearch/buildSearchLink.ts index 11ed519..88a394d 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/buildSearchLink.ts +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/buildSearchLink.ts @@ -1,5 +1,5 @@ -import { NestedURLSearchParams, coerceArray, isUnset } from '@/lib' -import { type InputParam } from './useAdvancedSearch' +import { NestedURLSearchParams, coerceArray, isUnset } from "@/lib" +import { type InputParam } from "./useAdvancedSearch" /** * Generate a URL for advanced searching @@ -36,8 +36,8 @@ function buildSearchLink( // Handle Date values if(value instanceof Date || (Array.isArray(value) && value[0] instanceof Date)) { const dateStr = coerceArray(value).reduce((str, date, i) => { - return `${str}${i === 0 ? '' : ','}${date.toISOString()}` - }, '') + return `${str}${i === 0 ? "" : ","}${date.toISOString()}` + }, "") localValues.set(param.name, dateStr) return } @@ -49,7 +49,7 @@ function buildSearchLink( if(localValues.isEmpty()) { return `${location.pathname}` } else { - localValues.set('adv', 'true') + localValues.set("adv", "true") return localValues.toString() } } diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/index.tsx b/app/frontend/Components/Table/SearchInput/AdvancedSearch/index.tsx index d69ece9..dd1d2fe 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/index.tsx +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/index.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react' -import { DoubleDownArrowIcon } from '@/Components/Icons' -import { useLayoutStore } from '@/lib/store' -import { useBooleanToggle } from '@/lib/hooks' -import { useClickOutside } from '@mantine/hooks' +import React, { useState } from "react" +import { DoubleDownArrowIcon } from "@/Components/Icons" +import useStore from "@/lib/store" +import { useBooleanToggle } from "@/lib/hooks" +import { useClickOutside } from "@mantine/hooks" import { ActionIcon, Paper, @@ -12,16 +12,16 @@ import { px, Tooltip, Box, -} from '@mantine/core' +} from "@mantine/core" -import cx from 'clsx' -import * as classes from './AdvancedSearch.css' +import cx from "clsx" +import * as classes from "./AdvancedSearch.css" const scaleY = { - in: { opacity: 1, transform: 'scaleY(1)' }, - out: { opacity: 0, transform: 'scaleY(0)' }, - common: { transformOrigin: 'top' }, - transitionProperty: 'transform, opacity', + in: { opacity: 1, transform: "scaleY(1)" }, + out: { opacity: 0, transform: "scaleY(0)" }, + common: { transformOrigin: "top" }, + transitionProperty: "transform, opacity", } interface AdvancedSearchProps { @@ -29,9 +29,9 @@ interface AdvancedSearchProps { } const AdvancedSearch = ({ children }: AdvancedSearchProps) => { - const { sidebarOpen } = useLayoutStore() + const { sidebarOpen } = useStore() const { primaryColor, other: { navbar: { width } } } = useMantineTheme() - const navBarWidth = width[sidebarOpen ? 'open' : 'closed'] + const navBarWidth = width[sidebarOpen ? "open" : "closed"] const [open, toggleOpen] = useBooleanToggle(false) const [searchButton, setSearchButton] = useState(null) @@ -75,7 +75,7 @@ const AdvancedSearch = ({ children }: AdvancedSearchProps) => { className={ cx(classes.paper) } style={ { ...styles, - left: rem(navBarWidth + Number(px('1rem'))), + left: rem(navBarWidth + Number(px("1rem"))), top: searchButton ? rem(searchButton.getBoundingClientRect().bottom + 10) : undefined, } } > @@ -91,4 +91,4 @@ const AdvancedSearch = ({ children }: AdvancedSearchProps) => { export default AdvancedSearch -export { default as useAdvancedSearch } from './useAdvancedSearch' +export { default as useAdvancedSearch } from "./useAdvancedSearch" diff --git a/app/frontend/Components/Table/SearchInput/AdvancedSearch/useAdvancedSearch.ts b/app/frontend/Components/Table/SearchInput/AdvancedSearch/useAdvancedSearch.ts index aa4715e..b6595a3 100644 --- a/app/frontend/Components/Table/SearchInput/AdvancedSearch/useAdvancedSearch.ts +++ b/app/frontend/Components/Table/SearchInput/AdvancedSearch/useAdvancedSearch.ts @@ -1,26 +1,27 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useLocation } from '@/lib/hooks' -import { isUnset } from '@/lib/forms' -import { router } from '@inertiajs/react' -import cx from 'clsx' -import { NestedURLSearchParams } from '@/lib' -import buildSearchLink from './buildSearchLink' +import { useCallback, useEffect, useMemo, useState } from "react" +import { useLocation } from "@/lib/hooks" +import { isUnset } from "@/lib/forms" +import { router } from "@inertiajs/react" +import { NestedURLSearchParams } from "@/lib" +import buildSearchLink from "./buildSearchLink" -type SpecialSearchTypes = 'date' +import cx from "clsx" + +type SpecialSearchTypes = "date" interface Options { path: string } -export type InputParam = { +export type InputParam = { name: string default?: T - dependent?: string|string[] + dependent?: string | string[] keyUpListener?: boolean type?: SpecialSearchTypes } -export type ParamValue = string|number|Date|Date[]|undefined|null +export type ParamValue = string | number | Date | Date[] | undefined | null /** * Hook for building advanced search interfaces @@ -35,7 +36,7 @@ const useAdvancedSearch = ( options?: Options, ) => { // TODO: Trying to infer keys from prop - type InputParamName = typeof inputParams[number]['name'] + type InputParamName = typeof inputParams[number]["name"] const location = useLocation() @@ -46,7 +47,7 @@ const useAdvancedSearch = ( inputParams.forEach(param => { switch(param?.type) { - case 'date': + case "date": finalParams.push({ name: `${param.name}[start]`, }) @@ -73,14 +74,14 @@ const useAdvancedSearch = ( (data: NestedURLSearchParams, param) => { // Handle special input types switch(param?.type) { - case 'date': - data.set(`${param.name}[type]`, 'exact') - data.set(`${param.name}[start]`, data.get(`${param.name}[start]`) || '') - data.set(`${param.name}[end]`, data.get(`${param.name}[end]`) || '') + case "date": + data.set(`${param.name}[type]`, "exact") + data.set(`${param.name}[start]`, data.get(`${param.name}[start]`) || "") + data.set(`${param.name}[end]`, data.get(`${param.name}[end]`) || "") return data default: - data.set(param.name, data.get(param.name) || param.default || '') + data.set(param.name, data.get(param.name) || param.default || "") } return data @@ -98,7 +99,7 @@ const useAdvancedSearch = ( const resetValues = useCallback(() => { setValues(prevValues => localInputParams.reduce( (data, param) => { - data.set(param.name, param.default ?? '') + data.set(param.name, param.default ?? "") return data }, prevValues.clone(), @@ -106,12 +107,12 @@ const useAdvancedSearch = ( }, [localInputParams]) // Method returned from hook to be passed to an input - const buildInputProps = (name: InputParamName) => { + const buildInputProps = (name: InputParamName) => { const param = localInputParams.find(param => param.name === name) let value: T switch(param?.type) { - case 'date': + case "date": // @ts-ignore value = new Date(values.get(name)) break @@ -124,7 +125,7 @@ const useAdvancedSearch = ( value, mb: 10, ...( param?.keyUpListener !== false && { onKeyUp: (e: React.KeyboardEvent) => { - if(e.key === 'Enter') { + if(e.key === "Enter") { router.get(searchLink, undefined, { preserveScroll: true }) } } }), diff --git a/app/frontend/Components/Table/SearchInput/ColumnPicker.tsx b/app/frontend/Components/Table/SearchInput/ColumnPicker.tsx index 706e1b5..c7feb29 100644 --- a/app/frontend/Components/Table/SearchInput/ColumnPicker.tsx +++ b/app/frontend/Components/Table/SearchInput/ColumnPicker.tsx @@ -1,16 +1,15 @@ -import React from 'react' -import { router } from '@inertiajs/react' -import { Routes } from '@/lib' -import axios from 'axios' -import { Menu } from '@/Components' -import { ColumnsIcon } from '@/Components/Icons' -import { Checkbox } from '@/Components/Inputs' -import { useTableContext } from '../TableContext' -import { Button } from '@mantine/core' -import { usePageProps } from '@/lib/hooks' +import { router } from "@inertiajs/react" +import { Routes } from "@/lib" +import axios from "axios" +import { Menu } from "@/Components" +import { ColumnsIcon } from "@/Components/Icons" +import { Checkbox } from "@/Components/Inputs" +import { useTableContext } from "../TableContext" +import { Button } from "@mantine/core" +import { usePageProps } from "@/lib/hooks" -import cx from 'clsx' -import * as classes from '../Table.css' +import cx from "clsx" +import * as classes from "../Table.css" const ColumnPicker = () => { const { auth: { user } } = usePageProps() @@ -19,7 +18,7 @@ const ColumnPicker = () => { if(!hideable || !model) return <> const handleChange = (e: React.ChangeEvent) => { - axios.patch( Routes.apiUpdateTablePreferences(user.id), { + axios.patch( Routes.apiUpdateTablePreferences(user.id!), { user: { table_preferences: { [model]: { @@ -30,7 +29,7 @@ const ColumnPicker = () => { }, }, }).then(() => { - router.reload({ only: ['auth'] }) + router.reload({ only: ["auth"] }) }) } @@ -44,7 +43,7 @@ const ColumnPicker = () => { { columns.filter(option => option.hideable).map(({ label, hideable }) => ( - + { - const urlSearchString = location.params.get('search') + const urlSearchString = location.params.get("search") // On first render, use URL search param as search value. // This should only trigger on page load when directly visited via a shared link e.g. @@ -49,7 +49,7 @@ const SearchInput = ({ columnPicker = true, advancedSearch }: SearchInputProps) } }) - const debouncedSearch = useCallback(debounce((path) => { + const debouncedSearch = useMemo(() => debounce((path) => { const options: VisitOptions = { replace: true, preserveScroll: true, @@ -61,7 +61,7 @@ const SearchInput = ({ columnPicker = true, advancedSearch }: SearchInputProps) setTableState({ searching: false }) }, } - if(model) options.only = [model, 'pagination'] + if(model) options.only = [model, "pagination"] router.get(path, {}, options) }, 500), [model, setTableState]) @@ -70,20 +70,24 @@ const SearchInput = ({ columnPicker = true, advancedSearch }: SearchInputProps) const url = new URL(window.location.href) if( - url.searchParams.get('search') === searchValue || - (url.searchParams.get('search') === null && searchValue === '') + url.searchParams.get("search") === searchValue || + (url.searchParams.get("search") === null && searchValue === "") ) return - if(searchValue === '') { - url.searchParams.delete('search') + if(searchValue === "") { + url.searchParams.delete("search") } else { - url.searchParams.set('search', searchValue ?? '') - url.searchParams.delete('page') + url.searchParams.set("search", searchValue ?? "") + url.searchParams.delete("page") } debouncedSearch(url.toString()) }, [debouncedSearch, searchValue]) + const handleClearInput = () => { + setSearchValue("") + } + return ( { advancedSearch && { advancedSearch } } @@ -92,14 +96,17 @@ const SearchInput = ({ columnPicker = true, advancedSearch }: SearchInputProps) id="search" value={ searchValue } onChange={ e => setSearchValue(e.target.value) } - rightSection={ searchValue !== '' && - - } + rightSection={ + searchValue !== "" && ( + + + + ) } leftSection={ } leftSectionPointerEvents="none" className={ classes.searchInput } - aria-label="Search" wrapper={ false } + aria-label="Search" /> { columnPicker && } diff --git a/app/frontend/Components/Table/Section.tsx b/app/frontend/Components/Table/Section.tsx index afce17e..d999c60 100644 --- a/app/frontend/Components/Table/Section.tsx +++ b/app/frontend/Components/Table/Section.tsx @@ -1,6 +1,5 @@ -import React from 'react' -import { Section } from '@/Components' -import * as classes from './Table.css' +import { Section } from "@/Components" +import * as classes from "./Table.css" const TableSection = ({ children }: { children: React.ReactNode }) => { return ( diff --git a/app/frontend/Components/Table/Table.css.ts b/app/frontend/Components/Table/Table.css.ts index c485977..b91330d 100644 --- a/app/frontend/Components/Table/Table.css.ts +++ b/app/frontend/Components/Table/Table.css.ts @@ -1,6 +1,6 @@ -import { vars, theme } from '@/lib/theme' -import { rem } from '@mantine/core' -import { css } from '@linaria/core' +import { vars, theme } from "@/lib/theme" +import { rem } from "@mantine/core" +import { css } from "@linaria/core" export const wrapper = css` overflow: auto; @@ -84,6 +84,10 @@ export const table = css` } } + &.actions { + width: 1px; + white-space: nowrap; + } } /* On small screens, collapse tables into "cards" */ @@ -129,7 +133,6 @@ export const section = css` export const searchWrapper = css` display: flex; flex: 1; - width: 100%; ` export const searchInput = css` diff --git a/app/frontend/Components/Table/Table.tsx b/app/frontend/Components/Table/Table.tsx index 24246ee..4e18235 100644 --- a/app/frontend/Components/Table/Table.tsx +++ b/app/frontend/Components/Table/Table.tsx @@ -1,21 +1,20 @@ -import React from 'react' -import { Table, type TableProps as MantineTableProps } from '@mantine/core' +import { Table, type TableProps as MantineTableProps } from "@mantine/core" -import Head from './Head' -import Body from './Body' -import RowIterator from './RowIterator' -import Row from './Row' -import Cell from './Td' -import HeadCell from './Th' -import Footer from './Footer' -import Pagination from './Pagination' -import TableProvider, { useTableContext } from './TableContext' -import TableSection from './Section' -import SearchInput from './SearchInput' -import ConditionalWrapper from '../ConditionalWrapper' +import Head from "./Head" +import Body from "./Body" +import RowIterator from "./RowIterator" +import Row from "./Row" +import Cell from "./Td" +import HeadCell from "./Th" +import Footer from "./Footer" +import Pagination from "./Pagination" +import TableProvider, { useTableContext } from "./TableContext" +import TableSection from "./Section" +import SearchInput from "./SearchInput" +import ConditionalWrapper from "../ConditionalWrapper" -import cx from 'clsx' -import * as classes from './Table.css' +import cx from "clsx" +import * as classes from "./Table.css" export interface TableProps extends MantineTableProps { fixed?: boolean @@ -65,7 +64,7 @@ const TableComponent: TableObject = ({ striped={ striped } highlightOnHover={ highlightOnHover } className={ cx(className, classes.table) } - style={ [wrapper ? { thead: { top: -10 } } : undefined, style] } + style={ [wrapper ? { thead: { top: - 10 } } : undefined, style] } { ...props } > { children } diff --git a/app/frontend/Components/Table/TableContext.tsx b/app/frontend/Components/Table/TableContext.tsx index a1bcd9e..470e3ee 100644 --- a/app/frontend/Components/Table/TableContext.tsx +++ b/app/frontend/Components/Table/TableContext.tsx @@ -1,13 +1,12 @@ -import React, { useReducer, useEffect } from 'react' -import { createContext } from '@/lib/hooks' -import { type Pagination } from '@/types' +import React, { useReducer, useEffect } from "react" +import { createContext } from "../../lib/hooks" /** * Table Section Context * Used by Cell component to determine which tag to use */ interface TableSectionContextProvider { - section: 'head'|'body'|'footer' + section: "head" | "body" | "footer" } const [useTableSectionContext, TableSectionContextProvider] = createContext() @@ -18,7 +17,7 @@ export { useTableSectionContext, TableSectionContextProvider } */ interface TableState { selectable: boolean - pagination?: Pagination + pagination?: Schema.Pagination rows?: Record[] columns: { hideable: string, label: string }[] selected: Set @@ -35,7 +34,7 @@ interface TableContextValues { interface TableContextProviderProps { children: React.ReactNode selectable?: boolean - pagination?: Pagination + pagination?: Schema.Pagination rows?: Record[] hideable?: boolean @@ -85,7 +84,7 @@ const TableProvider = ({ interface StatePreservingRowUpdaterProps { children: React.ReactElement rows?: Record[] - pagination?: Pagination + pagination?: Schema.Pagination } /** diff --git a/app/frontend/Components/Table/Td/BodyCell.tsx b/app/frontend/Components/Table/Td/BodyCell.tsx index a831f31..ac68518 100644 --- a/app/frontend/Components/Table/Td/BodyCell.tsx +++ b/app/frontend/Components/Table/Td/BodyCell.tsx @@ -1,8 +1,7 @@ -import React from 'react' -import { useTableContext } from '../TableContext' -import BodyCellWithContext from './BodyCellWithContext' -import { Table } from '@mantine/core' -import { type TableCellProps } from '.' +import { useTableContext } from "../TableContext" +import BodyCellWithContext from "./BodyCellWithContext" +import { Table } from "@mantine/core" +import { type TableCellProps } from "." const BodyCell = ({ children, ...props }: TableCellProps) => { const tableState = useTableContext(false) diff --git a/app/frontend/Components/Table/Td/BodyCellWithContext.tsx b/app/frontend/Components/Table/Td/BodyCellWithContext.tsx index 5ea7540..4406ec7 100644 --- a/app/frontend/Components/Table/Td/BodyCellWithContext.tsx +++ b/app/frontend/Components/Table/Td/BodyCellWithContext.tsx @@ -1,27 +1,21 @@ -import React, { useRef } from 'react' -import cx from 'clsx' -import { type TableCellProps } from '.' -import { Table } from '@mantine/core' +import React, { useRef } from "react" +import cx from "clsx" +import { type TableCellProps } from "." +import { Table } from "@mantine/core" -export interface BodyCellWithContextProps extends Omit { - hideable?: false|string +export interface BodyCellWithContextProps extends Omit { + hideable?: false | string model?: string } -const BodyCellWithContext = ({ - children, - fitContent, - hideable, - model, - className, - ...props -}: BodyCellWithContextProps) => { +const BodyCellWithContext = ({ children, nowrap, fitContent, hideable, model, style, className, ...props }: BodyCellWithContextProps) => { const tdRef = useRef(null) return ( { children } diff --git a/app/frontend/Components/Table/Td/index.tsx b/app/frontend/Components/Table/Td/index.tsx index 2c8bf10..d8060e0 100644 --- a/app/frontend/Components/Table/Td/index.tsx +++ b/app/frontend/Components/Table/Td/index.tsx @@ -1,14 +1,13 @@ -import React from 'react' -import { useTableContext } from '../TableContext' -import BodyCell from './BodyCell' -import { type TableTdProps } from '@mantine/core' -import { usePageProps } from '@/lib/hooks' +import { useTableContext } from "../TableContext" +import BodyCell from "./BodyCell" +import { type TableTdProps } from "@mantine/core" +import { usePageProps } from "@/lib/hooks" export interface TableCellProps extends TableTdProps { fitContent?: boolean sort?: string nowrap?: boolean - hideable?: false|string + hideable?: false | string ref?: React.RefObject } diff --git a/app/frontend/Components/Table/Th/HeadCell.tsx b/app/frontend/Components/Table/Th/HeadCell.tsx index 86926aa..094edee 100644 --- a/app/frontend/Components/Table/Th/HeadCell.tsx +++ b/app/frontend/Components/Table/Th/HeadCell.tsx @@ -1,8 +1,7 @@ -import React from 'react' -import { useTableContext } from '../TableContext' -import HeadCellWithContext from './HeadCellWithContext' -import { Table } from '@mantine/core' -import { type TableHeadCellProps } from '.' +import { useTableContext } from "../TableContext" +import HeadCellWithContext from "./HeadCellWithContext" +import { Table } from "@mantine/core" +import { type TableHeadCellProps } from "." const HeadCell = ({ children, ...props }: TableHeadCellProps) => { const tableState = useTableContext(false) diff --git a/app/frontend/Components/Table/Th/HeadCellWithContext.tsx b/app/frontend/Components/Table/Th/HeadCellWithContext.tsx index fb369e3..a149b0a 100644 --- a/app/frontend/Components/Table/Th/HeadCellWithContext.tsx +++ b/app/frontend/Components/Table/Th/HeadCellWithContext.tsx @@ -1,9 +1,9 @@ -import React, { useMemo, useRef } from 'react' -import { Link, Flex } from '@/Components' -import cx from 'clsx' -import { type TableHeadCellProps } from '.' -import { useLocation } from '@/lib/hooks' -import { Table } from '@mantine/core' +import React, { useMemo, useRef } from "react" +import { Link, Flex } from "@/Components" +import cx from "clsx" +import { type TableHeadCellProps } from "." +import { useLocation } from "@/lib/hooks" +import { Table } from "@mantine/core" interface HeadCellWithContextProps extends TableHeadCellProps { rows?: Record[] @@ -13,8 +13,10 @@ const HeadCellWithContext = ({ children, fitContent = false, sort, + nowrap = true, rows, hideable, + style, ...props }: HeadCellWithContextProps) => { const thRef = useRef(null) @@ -22,10 +24,10 @@ const HeadCellWithContext = ({ const localParams = new URLSearchParams(params) - const paramsSort = localParams.get('sort') - const paramsDirection = localParams.get('direction') + const paramsSort = localParams.get("sort") + const paramsDirection = localParams.get("direction") - const direction = paramsSort === sort && paramsDirection === 'asc' ? 'desc' : 'asc' + const direction = paramsSort === sort && paramsDirection === "asc" ? "desc" : "asc" const showSortLink: boolean = sort !== undefined && rows!.length > 1 @@ -34,13 +36,13 @@ const HeadCellWithContext = ({ if(!showSortLink) return undefined if(sort === undefined) { - localParams.delete('sort') + localParams.delete("sort") return undefined } - localParams.set('sort', sort) + localParams.set("sort", sort) - localParams.set('direction', direction) + localParams.set("direction", direction) return `${pathname}?${localParams.toString()}` }, [showSortLink, sort, direction, pathname]) @@ -49,10 +51,14 @@ const HeadCellWithContext = ({ diff --git a/app/frontend/Components/Table/Th/index.tsx b/app/frontend/Components/Table/Th/index.tsx index 97b0262..ecb8050 100644 --- a/app/frontend/Components/Table/Th/index.tsx +++ b/app/frontend/Components/Table/Th/index.tsx @@ -1,14 +1,13 @@ -import React from 'react' -import { useTableContext } from '../TableContext' -import HeadCell from './HeadCell' -import { type TableThProps } from '@mantine/core' -import { usePageProps } from '@/lib/hooks' +import { useTableContext } from "../TableContext" +import HeadCell from "./HeadCell" +import { type TableThProps } from "@mantine/core" +import { usePageProps } from "@/lib/hooks" export interface TableHeadCellProps extends TableThProps { fitContent?: boolean sort?: string nowrap?: boolean - hideable?: false|string + hideable?: false | string ref?: React.RefObject } diff --git a/app/frontend/Components/Table/index.ts b/app/frontend/Components/Table/index.ts index e16925d..055e297 100644 --- a/app/frontend/Components/Table/index.ts +++ b/app/frontend/Components/Table/index.ts @@ -1,5 +1,4 @@ -import Table, { type TableObject, type TableProps } from './Table' -export { default as useAdvancedSearch } from './SearchInput/AdvancedSearch/useAdvancedSearch' +import Table, { type TableObject } from "./Table" +export { default as useAdvancedSearch } from "./SearchInput/AdvancedSearch/useAdvancedSearch" export default Table as TableObject -export { TableProps } diff --git a/app/frontend/Components/Tabs/TabLink.tsx b/app/frontend/Components/Tabs/TabLink.tsx index 5670e27..e2208ac 100644 --- a/app/frontend/Components/Tabs/TabLink.tsx +++ b/app/frontend/Components/Tabs/TabLink.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import Link, { LinkProps } from '../Link' -import cx from 'clsx' +import React from "react" +import Link, { LinkProps } from "../Link" +import cx from "clsx" interface TabLinkProps extends LinkProps { - position?: undefined | 'right' + position?: undefined | "right" } const TabLink = ({ position, className, ...props }: TabLinkProps) => { diff --git a/app/frontend/Components/Tabs/Tabs.css.ts b/app/frontend/Components/Tabs/Tabs.css.ts index 4963db7..6359b23 100644 --- a/app/frontend/Components/Tabs/Tabs.css.ts +++ b/app/frontend/Components/Tabs/Tabs.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" export const tabs = css` height: 100%; diff --git a/app/frontend/Components/Tabs/UrlTabs.tsx b/app/frontend/Components/Tabs/UrlTabs.tsx index 0d0ddef..a25b71c 100644 --- a/app/frontend/Components/Tabs/UrlTabs.tsx +++ b/app/frontend/Components/Tabs/UrlTabs.tsx @@ -1,10 +1,10 @@ -import React, { useEffect } from 'react' -import { Tabs } from '@mantine/core' -import { type VisitOptions } from '@inertiajs/core' -import { router } from '@inertiajs/react' -import { TabsComponentProps } from '.' -import { coerceArray } from '@/lib' -import { useLocation } from '@/lib/hooks' +import React, { useEffect } from "react" +import { Tabs } from "@mantine/core" +import { type VisitOptions } from "@inertiajs/core" +import { router } from "@inertiajs/react" +import { TabsComponentProps } from "." +import { coerceArray } from "@/lib" +import { useLocation } from "@/lib/hooks" const UrlTabs = ({ children, onChange, defaultValue, dependencies, ...props }: TabsComponentProps) => { const { params } = useLocation() @@ -23,16 +23,16 @@ const UrlTabs = ({ children, onChange, defaultValue, dependencies, ...props }: T }, options || {})) } - const activeTab = params.get('tab') + const activeTab = params.get("tab") // Handle direct navigation to tabbed page useEffect(() => { if(!activeTab && defaultValue) { navigateTab(defaultValue, { replace: true }) } else { - document.addEventListener('inertia:navigate', function reloadActiveTab() { + document.addEventListener("inertia:navigate", function reloadActiveTab() { navigateTab(activeTab) - document.removeEventListener('inertia:navigate', reloadActiveTab) + document.removeEventListener("inertia:navigate", reloadActiveTab) }) } }, []) diff --git a/app/frontend/Components/Tabs/VerticalNavLayout.tsx b/app/frontend/Components/Tabs/VerticalNavLayout.tsx index 00bd424..274545d 100644 --- a/app/frontend/Components/Tabs/VerticalNavLayout.tsx +++ b/app/frontend/Components/Tabs/VerticalNavLayout.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react' -import { Page, Box, Section, Tabs } from '@/Components' -import { router } from '@inertiajs/react' -import { Paper, useMantineTheme } from '@mantine/core' -import { useViewportSize, useLocation } from '@/lib/hooks' -import { px } from '@/lib' +import React, { useState } from "react" +import { Page, Box, Section, Tabs } from "@/Components" +import { router } from "@inertiajs/react" +import { Paper, useMantineTheme } from "@mantine/core" +import { useViewportSize, useLocation } from "@/lib/hooks" +import { px } from "@/lib" export type Tab = { name: string @@ -40,7 +40,7 @@ const VerticalNavLayout = ({ children, tabs, title, routePrefix }: IVerticalNavL
@@ -63,8 +63,8 @@ const VerticalNavLayout = ({ children, tabs, title, routePrefix }: IVerticalNavL { tabs.map(tab => ( - - + + { children } diff --git a/app/frontend/Components/Tabs/index.tsx b/app/frontend/Components/Tabs/index.tsx index 0cf05fe..ea6ddd9 100644 --- a/app/frontend/Components/Tabs/index.tsx +++ b/app/frontend/Components/Tabs/index.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { Tabs, type TabsProps } from '@mantine/core' -import UrlTabs from './UrlTabs' -import TabLink from './TabLink' +import React from "react" +import { Tabs, type TabsProps } from "@mantine/core" +import UrlTabs from "./UrlTabs" +import TabLink from "./TabLink" -import cx from 'clsx' -import * as classes from './Tabs.css' +import cx from "clsx" +import * as classes from "./Tabs.css" export interface TabsComponentProps extends TabsProps { urlControlled?: boolean diff --git a/app/frontend/Components/index.ts b/app/frontend/Components/index.ts index 9752af8..59c448f 100644 --- a/app/frontend/Components/index.ts +++ b/app/frontend/Components/index.ts @@ -1,14 +1,16 @@ -export { default as Button } from './Button' -export { default as ConditionalWrapper } from './ConditionalWrapper' -export { default as DangerousHtml } from './DangerousHtml' -export { default as Link } from './Link' -export { default as Menu } from './Menu' -export { default as Page, type PageProps } from './Page' -export { default as RichTextEditor } from './RichTextEditor' -export { default as Section } from './Section' -export { default as Table, type TableProps } from './Table' -export { default as Label } from './Label' -export { default as Tabs } from './Tabs' +export { default as Button } from "./Button" +export { default as ConditionalWrapper } from "./ConditionalWrapper" +export { default as DangerousHtml } from "./DangerousHtml" +export { default as Flash } from "./Flash" +export { default as Link } from "./Link" +export { default as Menu } from "./Menu" +export { default as Page, type PageProps } from "./Page" +export { default as RichTextEditor } from "./RichTextEditor" +export { default as Section } from "./Section" +export { default as Table } from "./Table" +export { type TableProps } from "./Table/Table" +export { default as Label } from "./Label" +export { default as Tabs } from "./Tabs" // Export UI library components as a proxy to allow easy refactoring export { @@ -37,4 +39,4 @@ export { Title, Tooltip, ThemeIcon as Icon, -} from '@mantine/core' +} from "@mantine/core" diff --git a/app/frontend/Features/Controls/Control/Button/Base.tsx b/app/frontend/Features/Controls/Control/Button/Base.tsx index 673277e..f386f3d 100644 --- a/app/frontend/Features/Controls/Control/Button/Base.tsx +++ b/app/frontend/Features/Controls/Control/Button/Base.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import { Button } from '@/Components' -import { ElementProps, type ButtonProps } from '@mantine/core' -import { type ControlProps } from '..' -import { controlTitle } from '../lib' +import React from "react" +import { Button } from "@/Components" +import { ElementProps, type ButtonProps } from "@mantine/core" +import { type ControlProps } from ".." +import { controlTitle } from "../lib" -import cx from 'clsx' -import * as classes from '../../Controls.css' +import cx from "clsx" +import * as classes from "../../Controls.css" -export type ControlButtonBaseProps = ControlProps & ButtonProps & ElementProps<'button', keyof ButtonProps> & { } +export type ControlButtonBaseProps = ControlProps & ButtonProps & ElementProps<"button", keyof ButtonProps> & { } const ControlButtonBase = ({ children, control, disable, className, ...props }: ControlProps) => { return ( diff --git a/app/frontend/Features/Controls/Control/Button/Control.tsx b/app/frontend/Features/Controls/Control/Button/Control.tsx index 8fe0ca6..bb5255e 100644 --- a/app/frontend/Features/Controls/Control/Button/Control.tsx +++ b/app/frontend/Features/Controls/Control/Button/Control.tsx @@ -1,11 +1,11 @@ -import React from 'react' -import axios from 'axios' -import { useLocalStorage } from '@/lib/hooks' -import { controlRoute } from '../lib' -import Base, { type ControlButtonBaseProps } from './Base' +import React from "react" +import axios from "axios" +import { useLocalStorage } from "@/lib/hooks" +import { controlRoute } from "../lib" +import Base, { type ControlButtonBaseProps } from "./Base" -import cx from 'clsx' -import * as classes from '../../Controls.css' +import cx from "clsx" +import * as classes from "../../Controls.css" type ControlButtonProps = ControlButtonBaseProps & { edit?: false @@ -21,7 +21,7 @@ const ControlButton = ({ ...props }: ControlButtonProps) => { const [lastButtonClicked, setLastButtonClicked] = useLocalStorage({ - key: 'last-button-clicked', + key: "last-button-clicked", defaultValue: undefined, }) diff --git a/app/frontend/Features/Controls/Control/Button/Edit.tsx b/app/frontend/Features/Controls/Control/Button/Edit.tsx index ea0d67a..a13484e 100644 --- a/app/frontend/Features/Controls/Control/Button/Edit.tsx +++ b/app/frontend/Features/Controls/Control/Button/Edit.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import Base, { type ControlButtonBaseProps } from './Base' +import React from "react" +import Base, { type ControlButtonBaseProps } from "./Base" export type EditControlButtonProps = ControlButtonBaseProps & { edit: true diff --git a/app/frontend/Features/Controls/Control/Button/index.tsx b/app/frontend/Features/Controls/Control/Button/index.tsx index 0679840..bf2a980 100644 --- a/app/frontend/Features/Controls/Control/Button/index.tsx +++ b/app/frontend/Features/Controls/Control/Button/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { type ControlButtonBaseProps } from './Base' -import EditControlButton from './Edit' -import ControlButton from './Control' +import React from "react" +import { type ControlButtonBaseProps } from "./Base" +import EditControlButton from "./Edit" +import ControlButton from "./Control" export default ({ edit, control, ...props }: ControlButtonBaseProps) => { return edit ? diff --git a/app/frontend/Features/Controls/Control/EditControlWrapper.tsx b/app/frontend/Features/Controls/Control/EditControlWrapper.tsx index 09fde08..3fc9272 100644 --- a/app/frontend/Features/Controls/Control/EditControlWrapper.tsx +++ b/app/frontend/Features/Controls/Control/EditControlWrapper.tsx @@ -1,18 +1,18 @@ -import React from 'react' -import { Routes } from '@/lib' -import { router } from '@inertiajs/react' -import { Control, type ControlProps } from '@/Features/Controls' -import { useSortable } from '@dnd-kit/sortable' -import { CSS } from '@dnd-kit/utilities' -import { Box, BoxProps } from '@mantine/core' -import { EditIcon } from '@/Components/Icons' -import { modals } from '@mantine/modals' -import ControlForm from '../ControlForm' +import React from "react" +import { Routes } from "@/lib" +import { router } from "@inertiajs/react" +import { Control, type ControlProps } from "@/Features/Controls" +import { useSortable } from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" +import { Box, BoxProps } from "@mantine/core" +import { EditIcon } from "@/Components/Icons" +import { modals } from "@mantine/modals" +import ControlForm from "../ControlForm" -import cx from 'clsx' -import * as classes from '../Controls.css' +import cx from "clsx" +import * as classes from "../Controls.css" -interface EditControlWrapperProps extends Omit { +interface EditControlWrapperProps extends Omit { control: Schema.ControlsFormData } @@ -30,7 +30,7 @@ const EditControlWrapper = ({ children, control, ...props }: EditControlWrapperP e.preventDefault() modals.open({ - title: 'Edit Control', + title: "Edit Control", children: ( modals.closeAll() } - filter={ ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] } + filter={ ["control.id", "control.command", "control.updated_at", "control.created_at", "control.command_id", "control.protocol"] } // onSuccess={ () => { // onSuccess?.() // } } diff --git a/app/frontend/Features/Controls/Control/Slider/Base.tsx b/app/frontend/Features/Controls/Control/Slider/Base.tsx index d6c2ba0..6a6212e 100644 --- a/app/frontend/Features/Controls/Control/Slider/Base.tsx +++ b/app/frontend/Features/Controls/Control/Slider/Base.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Slider } from '@/Components' -import { ControlProps } from '..' -import { controlRoute, controlTitle } from '../lib' +import React from "react" +import { Slider } from "@/Components" +import { ControlProps } from ".." +import { controlRoute, controlTitle } from "../lib" export type ControlSliderBaseProps = ControlProps & {} diff --git a/app/frontend/Features/Controls/Control/Slider/Control.tsx b/app/frontend/Features/Controls/Control/Slider/Control.tsx index d87fe7b..4de64b4 100644 --- a/app/frontend/Features/Controls/Control/Slider/Control.tsx +++ b/app/frontend/Features/Controls/Control/Slider/Control.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import Base, { type ControlSliderBaseProps } from './Base' +import React from "react" +import Base, { type ControlSliderBaseProps } from "./Base" type ControlSliderProps = ControlSliderBaseProps & { edit?: false diff --git a/app/frontend/Features/Controls/Control/Slider/Edit.tsx b/app/frontend/Features/Controls/Control/Slider/Edit.tsx index 50cf54e..6b3e8a8 100644 --- a/app/frontend/Features/Controls/Control/Slider/Edit.tsx +++ b/app/frontend/Features/Controls/Control/Slider/Edit.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import Base, { type ControlSliderBaseProps } from './Base' +import React from "react" +import Base, { type ControlSliderBaseProps } from "./Base" type EditControlSliderProps = ControlSliderBaseProps & { edit: true diff --git a/app/frontend/Features/Controls/Control/Slider/index.tsx b/app/frontend/Features/Controls/Control/Slider/index.tsx index 9eb3758..32ea631 100644 --- a/app/frontend/Features/Controls/Control/Slider/index.tsx +++ b/app/frontend/Features/Controls/Control/Slider/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import EditControlSlider from './Edit' -import ControlSlider from './Control' -import { type ControlProps } from '..' +import React from "react" +import EditControlSlider from "./Edit" +import ControlSlider from "./Control" +import { type ControlProps } from ".." export default ({ edit, control, ...props }: ControlProps) => { return edit ? diff --git a/app/frontend/Features/Controls/Control/Spacer/Base.tsx b/app/frontend/Features/Controls/Control/Spacer/Base.tsx index 2ea61fe..6e5f28a 100644 --- a/app/frontend/Features/Controls/Control/Spacer/Base.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/Base.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Box } from '@/Components' -import { type ControlProps } from '..' +import React from "react" +import { Box } from "@/Components" +import { type ControlProps } from ".." -import cx from 'clsx' -import * as classes from '../../Controls.css' +import cx from "clsx" +import * as classes from "../../Controls.css" export type ControlSpacerBaseProps = ControlProps & {} diff --git a/app/frontend/Features/Controls/Control/Spacer/Control.tsx b/app/frontend/Features/Controls/Control/Spacer/Control.tsx index b53cf9a..826a7e5 100644 --- a/app/frontend/Features/Controls/Control/Spacer/Control.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/Control.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import Base, { type ControlSpacerBaseProps } from './Base' +import React from "react" +import Base, { type ControlSpacerBaseProps } from "./Base" type ControlSpacerProps = ControlSpacerBaseProps & { edit?: false diff --git a/app/frontend/Features/Controls/Control/Spacer/Edit.tsx b/app/frontend/Features/Controls/Control/Spacer/Edit.tsx index bc809a1..5e58a44 100644 --- a/app/frontend/Features/Controls/Control/Spacer/Edit.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/Edit.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import Base from './Base' -import { type ControlProps } from '..' +import React from "react" +import Base from "./Base" +import { type ControlProps } from ".." type EditControlSpacerProps = ControlProps & { edit: true diff --git a/app/frontend/Features/Controls/Control/Spacer/index.tsx b/app/frontend/Features/Controls/Control/Spacer/index.tsx index 7af29b0..f3e63c2 100644 --- a/app/frontend/Features/Controls/Control/Spacer/index.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import EditControlSpacer from './Edit' -import ControlSpacer from './Control' -import { type ControlProps } from '..' +import React from "react" +import EditControlSpacer from "./Edit" +import ControlSpacer from "./Control" +import { type ControlProps } from ".." export default ({ edit, control, ...props }: ControlProps) => { return edit ? diff --git a/app/frontend/Features/Controls/Control/index.tsx b/app/frontend/Features/Controls/Control/index.tsx index bcff6ae..09fbe07 100644 --- a/app/frontend/Features/Controls/Control/index.tsx +++ b/app/frontend/Features/Controls/Control/index.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import ButtonControl from './Button' -import SliderControl from './Slider' -import SpacerControl from './Spacer' -import { type BoxProps } from '@mantine/core' -import { ConditionalWrapper, Box } from '@/Components' -import EditControlWrapper from './EditControlWrapper' +import React from "react" +import ButtonControl from "./Button" +import SliderControl from "./Slider" +import SpacerControl from "./Spacer" +import { type BoxProps } from "@mantine/core" +import { ConditionalWrapper, Box } from "@/Components" +import EditControlWrapper from "./EditControlWrapper" -import cx from 'clsx' -import * as classes from '../Controls.css' +import cx from "clsx" +import * as classes from "../Controls.css" export interface ControlPropsBase extends BoxProps { children?: React.ReactNode @@ -26,7 +26,7 @@ type ControlPropsShow = ControlPropsBase & { control: Schema.ControlsShow } -type ControlComponent = (props: Omit & { +type ControlComponent = (props: Omit & { edit: boolean control: Schema.ControlsFormData | Schema.ControlsShow }) => React.ReactNode @@ -37,18 +37,18 @@ const Control = ({ control, edit = false, wrapper = true, className, ...props }: let ControlComponent: ControlComponent switch(control.control_type) { - case 'button': + case "button": ControlComponent = ButtonControl as ControlComponent - break; + break - case 'slider': + case "slider": ControlComponent = SliderControl as ControlComponent - break; + break - case 'spacer': + case "spacer": ControlComponent = SpacerControl as ControlComponent - break; + break default: ControlComponent = React.Fragment diff --git a/app/frontend/Features/Controls/Control/lib.ts b/app/frontend/Features/Controls/Control/lib.ts index 5bdd495..de59016 100644 --- a/app/frontend/Features/Controls/Control/lib.ts +++ b/app/frontend/Features/Controls/Control/lib.ts @@ -1,4 +1,4 @@ -import { Routes } from '@/lib' +import { Routes } from "@/lib" export const controlRoute = (control: Partial) => { if(control?.protocol?.slug) { diff --git a/app/frontend/Features/Controls/ControlForm.tsx b/app/frontend/Features/Controls/ControlForm.tsx index f8e418f..e59f6fb 100644 --- a/app/frontend/Features/Controls/ControlForm.tsx +++ b/app/frontend/Features/Controls/ControlForm.tsx @@ -1,20 +1,20 @@ -import React, { useState } from 'react' -import { Code, Grid, Paper, ScrollArea, Text } from '@/Components' -import { Form, TextInput, Submit, SwatchInput, FormConsumer, Radio } from '@/Components/Form' -import { ProtocolDropdown } from '@/Components/Dropdowns' -import { useGetProtocol } from '@/queries' -import { FormProps } from 'use-inertia-form' +import React, { useState } from "react" +import { Code, Grid, Paper, ScrollArea, Text } from "@/Components" +import { Form, TextInput, Submit, SwatchInput, FormConsumer, Radio } from "@/Components/Form" +import { ProtocolDropdown } from "@/Components/Dropdowns" +import { useGetProtocol } from "@/queries" +import { FormProps } from "use-inertia-form" type EditControlFormData = { control: Schema.ControlsFormData } -export interface EditControlFormProps extends Omit, 'data'> { +export interface EditControlFormProps extends Omit, "data"> { control?: Schema.ControlsFormData } const EditControlForm = ({ control, ...props }: EditControlFormProps) => { - const [showingProtocolSlug, setShowingProtocolSlug] = useState(control?.protocol?.slug || '') + const [showingProtocolSlug, setShowingProtocolSlug] = useState(control?.protocol?.slug || "") const { data } = useGetProtocol({ slug: showingProtocolSlug }, { initialData: control?.protocol || {}, @@ -31,14 +31,14 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { - { control?.control_type === 'slider' && <> + { control?.control_type === "slider" && <> } >{ ({ data }) => <> - { data.control.control_type !== 'spacer' && + { data.control.control_type !== "spacer" && { const option = options.find(option => option.value === protocol) @@ -55,7 +55,7 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { { data.commands?.map(command => ( - + { command.address } )) @@ -66,7 +66,7 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { >{ ({ data }) => <> - { data.control.control_type !== 'spacer' && + { data.control.control_type !== "spacer" && } @@ -80,7 +80,7 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { - { control?.id ? 'Update' : 'Create' } Control + { control?.id ? "Update" : "Create" } Control diff --git a/app/frontend/Features/Controls/Controls.css.ts b/app/frontend/Features/Controls/Controls.css.ts index f6382a8..ec8d3c4 100644 --- a/app/frontend/Features/Controls/Controls.css.ts +++ b/app/frontend/Features/Controls/Controls.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib' -import { css } from '@linaria/core' +import { vars } from "@/lib" +import { css } from "@linaria/core" const highlightBorderPx = 4 diff --git a/app/frontend/Features/Controls/_AddControlsInterface/AddControlsInterface.css.ts b/app/frontend/Features/Controls/_AddControlsInterface/AddControlsInterface.css.ts index 1814c91..8dd2451 100644 --- a/app/frontend/Features/Controls/_AddControlsInterface/AddControlsInterface.css.ts +++ b/app/frontend/Features/Controls/_AddControlsInterface/AddControlsInterface.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" export const controls = css` position: fixed; diff --git a/app/frontend/Features/Controls/_AddControlsInterface/ButtonControl.tsx b/app/frontend/Features/Controls/_AddControlsInterface/ButtonControl.tsx index 4159ee5..26ea97b 100644 --- a/app/frontend/Features/Controls/_AddControlsInterface/ButtonControl.tsx +++ b/app/frontend/Features/Controls/_AddControlsInterface/ButtonControl.tsx @@ -1,12 +1,12 @@ -import React from 'react' -import { useDraggable } from '@dnd-kit/core' -import { Button, ButtonProps } from '@mantine/core' +import React from "react" +import { useDraggable } from "@dnd-kit/core" +import { Button, ButtonProps } from "@mantine/core" interface ButtonControlProps extends ButtonProps { } const ButtonControl = ({ ...props }: ButtonControlProps) => { const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: 'button', + id: "button", }) const style = transform diff --git a/app/frontend/Features/Controls/_AddControlsInterface/SliderControl.tsx b/app/frontend/Features/Controls/_AddControlsInterface/SliderControl.tsx index 67622a6..d99051f 100644 --- a/app/frontend/Features/Controls/_AddControlsInterface/SliderControl.tsx +++ b/app/frontend/Features/Controls/_AddControlsInterface/SliderControl.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { useDraggable } from '@dnd-kit/core' -import { Slider, Text } from '@mantine/core' +import React from "react" +import { useDraggable } from "@dnd-kit/core" +import { Slider, Text } from "@mantine/core" const SliderControl = () => { const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: 'slider', + id: "slider", }) const style = transform diff --git a/app/frontend/Features/Controls/_AddControlsInterface/SpacerControl.tsx b/app/frontend/Features/Controls/_AddControlsInterface/SpacerControl.tsx index 6d9d5d8..e75384c 100644 --- a/app/frontend/Features/Controls/_AddControlsInterface/SpacerControl.tsx +++ b/app/frontend/Features/Controls/_AddControlsInterface/SpacerControl.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { useDraggable } from '@dnd-kit/core' -import { Box, Slider, Text } from '@mantine/core' +import React from "react" +import { useDraggable } from "@dnd-kit/core" +import { Box, Slider, Text } from "@mantine/core" const SpacerControl = () => { const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: 'spacer', + id: "spacer", }) const style = transform diff --git a/app/frontend/Features/Controls/_AddControlsInterface/index.tsx b/app/frontend/Features/Controls/_AddControlsInterface/index.tsx index 4d20092..69d1322 100644 --- a/app/frontend/Features/Controls/_AddControlsInterface/index.tsx +++ b/app/frontend/Features/Controls/_AddControlsInterface/index.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { Paper } from '@mantine/core' -import cx from 'clsx' -import * as classes from './AddControlsInterface.css' -import ButtonControl from './ButtonControl' -import SliderControl from './SliderControl' -import SpacerControl from './SpacerControl' +import React from "react" +import { Paper } from "@mantine/core" +import cx from "clsx" +import * as classes from "./AddControlsInterface.css" +import ButtonControl from "./ButtonControl" +import SliderControl from "./SliderControl" +import SpacerControl from "./SpacerControl" const AddControlsInterface = () => { return ( diff --git a/app/frontend/Features/Controls/index.ts b/app/frontend/Features/Controls/index.ts index aadadb9..c58f065 100644 --- a/app/frontend/Features/Controls/index.ts +++ b/app/frontend/Features/Controls/index.ts @@ -1,3 +1,3 @@ -export { default as AddControlsInterface } from './_AddControlsInterface' -export { default as Control, type ControlProps } from './Control' -export { default as ControlForm } from './ControlForm' +export { default as AddControlsInterface } from "./_AddControlsInterface" +export { default as Control, type ControlProps } from "./Control" +export { default as ControlForm } from "./ControlForm" diff --git a/app/frontend/Features/IndexPageTemplate/IndexPage.css.ts b/app/frontend/Features/IndexPageTemplate/IndexPage.css.ts index 3c5c4f3..96fb60b 100644 --- a/app/frontend/Features/IndexPageTemplate/IndexPage.css.ts +++ b/app/frontend/Features/IndexPageTemplate/IndexPage.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" export const title = css` flex: 1 1 100%; diff --git a/app/frontend/Features/IndexPageTemplate/TableTitleSection.tsx b/app/frontend/Features/IndexPageTemplate/TableTitleSection.tsx index cc1a2c0..33ba678 100644 --- a/app/frontend/Features/IndexPageTemplate/TableTitleSection.tsx +++ b/app/frontend/Features/IndexPageTemplate/TableTitleSection.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { useTableContext } from '@/Components/Table/TableContext' -import { Title, Group, Divider } from '@mantine/core' -import { Menu } from '@/Components' -import { TrashIcon } from '@/Components/Icons' -import { router } from '@inertiajs/react' -import { IconType } from 'react-icons' +import React from "react" +import { useTableContext } from "@/Components/Table/TableContext" +import { Title, Group, Divider } from "@mantine/core" +import { Menu } from "@/Components" +import { TrashIcon } from "@/Components/Icons" +import { router } from "@inertiajs/react" +import { IconType } from "react-icons" // import * as classes from './IndexPage.css' @@ -25,7 +25,7 @@ const IndexTableTitleSection = ({ children, title, deleteRoute, menuOptions }: I const deleteRecords = () => { router.visit(deleteRoute, { - method: 'delete', + method: "delete", data: { ids: Array.from(selected) }, }) } diff --git a/app/frontend/Features/IndexPageTemplate/index.tsx b/app/frontend/Features/IndexPageTemplate/index.tsx index 72a758a..593d33d 100644 --- a/app/frontend/Features/IndexPageTemplate/index.tsx +++ b/app/frontend/Features/IndexPageTemplate/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Page, Table } from '@/Components' -import TableTitleSection, { IIndexTableTitleSectionProps } from './TableTitleSection' -import { type Pagination } from '@/types' +import React from "react" +import { Page, Table } from "@/Components" +import TableTitleSection, { IIndexTableTitleSectionProps } from "./TableTitleSection" +import { type Pagination } from "@/types" interface IIndexPageTemplateProps extends IIndexTableTitleSectionProps { model: string diff --git a/app/frontend/Features/index.ts b/app/frontend/Features/index.ts index f43e88e..0a6b6c8 100644 --- a/app/frontend/Features/index.ts +++ b/app/frontend/Features/index.ts @@ -1,2 +1,2 @@ -export { default as AddControlsInterface } from './Controls/_AddControlsInterface' -export { default as IndexPageTemplate } from './IndexPageTemplate' +export { default as AddControlsInterface } from "./Controls/_AddControlsInterface" +export { default as IndexPageTemplate } from "./IndexPageTemplate" diff --git a/app/frontend/Layouts/AppLayout/AppLayout.css.ts b/app/frontend/Layouts/AppLayout/AppLayout.css.ts index 5b5de6a..a35b691 100644 --- a/app/frontend/Layouts/AppLayout/AppLayout.css.ts +++ b/app/frontend/Layouts/AppLayout/AppLayout.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" export const layout = css` display: flex; diff --git a/app/frontend/Layouts/AppLayout/index.tsx b/app/frontend/Layouts/AppLayout/index.tsx index 783a345..2d120e1 100644 --- a/app/frontend/Layouts/AppLayout/index.tsx +++ b/app/frontend/Layouts/AppLayout/index.tsx @@ -1,13 +1,14 @@ -import React from 'react' -import { AppShell, Burger, Container, Divider, Link, Menu, Title } from '@/Components' -import { useAuth } from '@/lib/hooks' -import { Routes } from '@/lib' +import React from "react" +import { AppShell, Burger, Container, Divider, Link, Menu, Title } from "@/Components" +import { ToggleColorSchemeButton } from "@/Components/Button" +import { HomeIcon } from "@/Components/Icons" +import { Routes } from "@/lib" +import { useAuth } from "@/lib/hooks" +import { LayoutProps } from "../index" -import * as classes from './AppLayout.css' -import { HomeIcon } from '@/Components/Icons' -import { ToggleColorSchemeButton } from '@/Components/Button' +import * as classes from "./AppLayout.css" -const AppLayout = ({ children }: { children: any }) => { +const AppLayout = ({ children }: LayoutProps) => { const { isLoggedIn } = useAuth() return ( diff --git a/app/frontend/Layouts/AuthLayout/AuthLayout.css.ts b/app/frontend/Layouts/AuthLayout/AuthLayout.css.ts index 65afcb9..0231504 100644 --- a/app/frontend/Layouts/AuthLayout/AuthLayout.css.ts +++ b/app/frontend/Layouts/AuthLayout/AuthLayout.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" export const authLayout = css` height: 100%; diff --git a/app/frontend/Layouts/AuthLayout/index.tsx b/app/frontend/Layouts/AuthLayout/index.tsx index 56cdc34..ac47093 100644 --- a/app/frontend/Layouts/AuthLayout/index.tsx +++ b/app/frontend/Layouts/AuthLayout/index.tsx @@ -1,6 +1,8 @@ -import React from 'react' -import { Box, Center, Flex, Paper } from '@/Components' -import * as classes from './AuthLayout.css' +import React from "react" +import { Box, Center, Flex, Paper } from "@/Components" + +import cx from "clsx" +import * as classes from "./AuthLayout.css" interface LayoutProps { children: any @@ -8,7 +10,7 @@ interface LayoutProps { const Layout = ({ children }: LayoutProps) => { return ( - +
{ children } diff --git a/app/frontend/Layouts/Providers/ContrastingColorsSetup.tsx b/app/frontend/Layouts/Providers/ContrastingColorsSetup.tsx new file mode 100644 index 0000000..82de4ad --- /dev/null +++ b/app/frontend/Layouts/Providers/ContrastingColorsSetup.tsx @@ -0,0 +1,17 @@ +import { useEffect } from "react" +import useStore from "@/lib/store" +import { useComputedColorScheme, useMantineTheme } from "@mantine/core" + +const ContrastingColorsSetup = () => { + const theme = useMantineTheme() + const colorScheme = useComputedColorScheme() + const setThemeData = useStore((state) => state.setThemeData) + + useEffect(() => { + setThemeData(theme, colorScheme) + }, [theme, colorScheme, setThemeData]) + + return <> +} + +export default ContrastingColorsSetup diff --git a/app/frontend/Layouts/Providers/IconProvider.tsx b/app/frontend/Layouts/Providers/IconProvider.tsx index 8be71b8..f0a7ac4 100644 --- a/app/frontend/Layouts/Providers/IconProvider.tsx +++ b/app/frontend/Layouts/Providers/IconProvider.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import { IconContext } from 'react-icons' +import React from "react" +import { IconContext } from "react-icons" interface IIconProviderProps { children: React.ReactNode @@ -7,8 +7,8 @@ interface IIconProviderProps { } const IconProvider = ({ children, value = { - className: 'react-icon', - size: '1rem', + className: "react-icon", + size: "1rem", } }: IIconProviderProps) => { return ( diff --git a/app/frontend/Layouts/Providers/QueryProvider.tsx b/app/frontend/Layouts/Providers/QueryProvider.tsx index 59bc564..aba3888 100644 --- a/app/frontend/Layouts/Providers/QueryProvider.tsx +++ b/app/frontend/Layouts/Providers/QueryProvider.tsx @@ -1,6 +1,5 @@ -import React from 'react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { ReactQueryDevtools } from "@tanstack/react-query-devtools" interface QueryProviderProps { children: React.ReactNode @@ -8,12 +7,10 @@ interface QueryProviderProps { const queryClient = new QueryClient() -const isDev = false // process.env.NODE_ENV && process?.env?.NODE_ENV === 'development' - const QueryProvider = ({ children }: QueryProviderProps) => { return ( - { isDev && } + { process.env.NODE_ENV && process.env.NODE_ENV === "development" && } { children } ) diff --git a/app/frontend/Layouts/Providers/UiFrameworkProvider.tsx b/app/frontend/Layouts/Providers/UiFrameworkProvider.tsx index 3960e52..28d314a 100644 --- a/app/frontend/Layouts/Providers/UiFrameworkProvider.tsx +++ b/app/frontend/Layouts/Providers/UiFrameworkProvider.tsx @@ -1,17 +1,19 @@ -import React, { useEffect, useMemo } from 'react' -import { MantineProvider, createTheme, px, type CSSVariablesResolver } from '@mantine/core' -import { type CSSVariables } from '@mantine/core/lib/core/MantineProvider/convert-css-variables/css-variables-object-to-string' -import { ModalsProvider } from '@mantine/modals' -import { Notifications } from '@mantine/notifications' -import { theme as themeObject, vars } from '@/lib/theme' -import useLayoutStore from '@/lib/store/LayoutStore' -import { toKebabCase } from '@/lib' +import React, { useMemo } from "react" +import { MantineProvider, createTheme, type CSSVariablesResolver } from "@mantine/core" +import { type CSSVariables } from "@mantine/core/lib/core/MantineProvider/convert-css-variables/css-variables-object-to-string" +import { ModalsProvider } from "@mantine/modals" +import { Notifications } from "@mantine/notifications" +import { theme as themeObject, vars } from "@/lib/theme" +import useStore from "@/lib/store" +import { toKebabCase } from "@/lib" +import { useInit } from "@/lib/hooks" +import { Flash } from "@/Components" const UiFrameworkProvider = ({ children }: { children: React.ReactNode }) => { /** * Primary color customization */ - const { primaryColor } = useLayoutStore() + const { primaryColor } = useStore() const theme = useMemo(() => createTheme({ ...themeObject, primaryColor }), [primaryColor]) @@ -39,19 +41,12 @@ const UiFrameworkProvider = ({ children }: { children: React.ReactNode }) => { }, [primaryColor]) - useEffect(() => { - /* eslint-disable no-console */ - if(process.env.NODE_ENV && process.env.NODE_ENV === 'development') { - console.log({ theme }) + useInit(() => { + if(import.meta.env.MODE === "development") { console.log({ vars }) - - console.log({ breakpointsPx: Object.fromEntries( - Object.entries(theme.breakpoints ?? []).map(([key, val]) => [key, px(val)]), - ) }) } - /* eslint-enable */ - }, []) - + }) + console.log({ children }) return ( { cssVariablesResolver={ cssVariablesResolver } > - + + { children } diff --git a/app/frontend/Layouts/Providers/global.css b/app/frontend/Layouts/Providers/global.css deleted file mode 100644 index c7971a0..0000000 --- a/app/frontend/Layouts/Providers/global.css +++ /dev/null @@ -1,18 +0,0 @@ -html, body, #app { - height: 100%; -} - - -/* *::selection { - background-color: var(--mantine-theme-primary-color-2); -} */ - -.hidden { - display: 'none'; -} - -/* .fullHeight { - display: 'flex'; - flexDirection: 'column'; - height: `calc(100vh - ${theme.other.header.height}px - ${theme.other.footer.height}px - 20px)`; -} */ diff --git a/app/frontend/Layouts/Providers/global.css.ts b/app/frontend/Layouts/Providers/global.css.ts new file mode 100644 index 0000000..221df3e --- /dev/null +++ b/app/frontend/Layouts/Providers/global.css.ts @@ -0,0 +1,41 @@ +import { css } from "@linaria/core" +import { vars } from "@/lib/theme" + +export const globals = css` + :global() { + html, body, #app { + height: 100%; + } + + *::selection { + background-color: ${vars.colors.primary[2]}; // [2] + } + + .hidden { + display: none; + } + + .fullHeight { + display: flex; + flex-direction: column; + } + + label { + font-size: 1rem; + } + + em { + font-style: italic; + } + + b { + font-weight: bold; + } + + strong { + font-weight: bold; + } + } + + +` diff --git a/app/frontend/Layouts/Providers/index.tsx b/app/frontend/Layouts/Providers/index.tsx index 625e06c..bcdbcfb 100644 --- a/app/frontend/Layouts/Providers/index.tsx +++ b/app/frontend/Layouts/Providers/index.tsx @@ -1,13 +1,15 @@ -import React from 'react' -import IconProvider from './IconProvider' -import UiFrameworkProvider from './UiFrameworkProvider' +import React from "react" +import IconProvider from "./IconProvider" +import UiFrameworkProvider from "./UiFrameworkProvider" +import QueryProvider from "./QueryProvider" +import ContrastingColorsSetup from "./ContrastingColorsSetup" -import './reset.css' -import '@mantine/core/styles.css' -import '@mantine/tiptap/styles.css' -import '@mantine/notifications/styles.css' -import './global.css' -import QueryProvider from './QueryProvider' +import "./reset.css" +import "./global.css" +import "@mantine/core/styles.css" +import "@mantine/tiptap/styles.css" +import "@mantine/dates/styles.css" +import "@mantine/notifications/styles.css" interface IProviderProps { children?: React.ReactNode @@ -17,6 +19,7 @@ const Providers = ({ children }: IProviderProps) => { return ( + { children } diff --git a/app/frontend/Layouts/PublicLayout/index.tsx b/app/frontend/Layouts/PublicLayout/index.tsx index 4516b6e..ff5b5cd 100644 --- a/app/frontend/Layouts/PublicLayout/index.tsx +++ b/app/frontend/Layouts/PublicLayout/index.tsx @@ -1,15 +1,12 @@ -import React from 'react' -import { useDisclosure } from '@mantine/hooks' -import { AppShell, Burger, Skeleton } from '@mantine/core' -import { Group, Link, Box } from '@/Components' -import { CircleDotIcon } from '@/Components/Icons' -import { Routes } from '@/lib' +import React from "react" +import { useDisclosure } from "@mantine/hooks" +import { AppShell, Burger, Skeleton } from "@mantine/core" +import { Group, Link, Box } from "@/Components" +import { CircleDotIcon } from "@/Components/Icons" +import { Routes } from "@/lib" +import { LayoutProps } from "../index" -interface PublicLayoutProps { - children: any -} - -const PublicLayout: React.FC = ({ children }) => { +const PublicLayout = ({ children }: LayoutProps) => { const [opened, { toggle }] = useDisclosure() return ( @@ -17,7 +14,7 @@ const PublicLayout: React.FC = ({ children }) => { header={ { height: { base: 30, md: 50, lg: 60 } } } navbar={ { width: { base: 100, md: 200, lg: 300 }, - breakpoint: 'sm', + breakpoint: "sm", collapsed: { mobile: !opened }, } } padding="md" @@ -34,7 +31,7 @@ const PublicLayout: React.FC = ({ children }) => { - Navigation + Navigation { Array(15) .fill(0) .map((_, index) => ( diff --git a/app/frontend/Layouts/index.tsx b/app/frontend/Layouts/index.tsx index b8190d9..d5cbcbb 100644 --- a/app/frontend/Layouts/index.tsx +++ b/app/frontend/Layouts/index.tsx @@ -1,30 +1,30 @@ -import React from 'react' -import { type PageProps } from '@inertiajs/core' -import Providers from '@/Layouts/Providers' -import { Flash } from '@/Components/Flash' +import Providers from "@/Layouts/Providers" +import { Flash } from "@/Components" -import AppLayout from './AppLayout' -import AuthLayout from './AuthLayout' -import PublicLayout from './PublicLayout' +import BareAppLayout from "./AppLayout" +import BareAuthLayout from "./AuthLayout" +import BarePublicLayout from "./PublicLayout" -import dayjs from 'dayjs' -import localizedFormat from 'dayjs/plugin/localizedFormat' -import duration from 'dayjs/plugin/duration' -import relativeTime from 'dayjs/plugin/relativeTime' +import dayjs from "dayjs" +import localizedFormat from "dayjs/plugin/localizedFormat" +import duration from "dayjs/plugin/duration" +import relativeTime from "dayjs/plugin/relativeTime" dayjs.extend(localizedFormat) dayjs.extend(duration) dayjs.extend(relativeTime) -interface LayoutWrapperProps { - children: React.ReactNode +export const LAYOUTS = { + "auth": "auth", + "app": "app", + "public": "public", } -interface InertiaPageProps extends PageProps { - props: LayoutWrapperProps +export interface LayoutProps { + children: any } -const LayoutWrapper = ({ children }: LayoutWrapperProps) => { +export const LayoutWrapper = ({ children }: LayoutProps) => { return ( @@ -33,32 +33,26 @@ const LayoutWrapper = ({ children }: LayoutWrapperProps) => { ) } -const AppLayoutLayout = (page: InertiaPageProps) => { +export const AppLayout = ({ children }: LayoutProps) => { return ( - { page } + { children } ) } -const AuthLayoutLayout = (page: InertiaPageProps) => { +export const AuthLayout = ({ children }: LayoutProps) => { return ( - { page } + { children } ) } -const PublicLayoutLayout = (page: InertiaPageProps) => { +export const PublicLayout = ({ children }: LayoutProps) => { return ( - { page } + { children } ) } - -export { - AppLayoutLayout as AppLayout, - AuthLayoutLayout as AuthLayout, - PublicLayoutLayout as PublicLayout, -} diff --git a/app/frontend/Pages/Commands/Edit/index.tsx b/app/frontend/Pages/Commands/Edit/index.tsx index e1b9ddf..33ade38 100644 --- a/app/frontend/Pages/Commands/Edit/index.tsx +++ b/app/frontend/Pages/Commands/Edit/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import CommandsForm from '../Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import CommandsForm from "../Form" interface IEditCommandProps { command: Schema.CommandsEdit diff --git a/app/frontend/Pages/Commands/Form.tsx b/app/frontend/Pages/Commands/Form.tsx index 3e678f2..552fa22 100644 --- a/app/frontend/Pages/Commands/Form.tsx +++ b/app/frontend/Pages/Commands/Form.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { Grid, Text } from '@/Components' -import { Form, TextInput, Submit, RichText, Checkbox, DynamicInputs } from '@/Components/Form' -import { type HTTPVerb, type UseFormProps } from 'use-inertia-form' -import { CommandPayloadTypesDropdown, ServerDropdown } from '@/Components/Dropdowns' -import { exclude } from '@/lib' -import { useListState } from '@mantine/hooks' +import React from "react" +import { Grid, Text } from "@/Components" +import { Form, TextInput, Submit, RichText, Checkbox, DynamicInputs } from "@/Components/Form" +import { type HTTPVerb, type UseFormProps } from "use-inertia-form" +import { CommandPayloadTypesDropdown, ServerDropdown } from "@/Components/Dropdowns" +import { exclude } from "@/lib" +import { useListState } from "@mantine/hooks" type CommandFormData = { command: Schema.CommandsFormData @@ -13,22 +13,22 @@ type CommandFormData = { export interface CommandFormProps { to: string method?: HTTPVerb - onSubmit?: (object: UseFormProps) => boolean|void + onSubmit?: (object: UseFormProps) => boolean | void command?: Schema.CommandsFormData } const emptyCommand: Partial = { - title: '', + title: "", server_id: undefined, - address: '', - description: '', + address: "", + description: "", payload_type: undefined, allow_custom_value: false, command_values: [], } -const CommandForm = ({ method = 'post', command, ...props }: CommandFormProps) => { - const [deletedValues, deletedValueHandlers] = useListState<{ id: string|number, _destroy: boolean}>() +const CommandForm = ({ method = "post", command, ...props }: CommandFormProps) => { + const [deletedValues, deletedValueHandlers] = useListState<{ id: string | number, _destroy: boolean }>() const handleRemoveCommandValue = (record: Schema.CommandValue) => { if(!record?.id) return @@ -48,7 +48,7 @@ const CommandForm = ({ method = 'post', command, ...props }: CommandFormProps) = } } - const formCommand = exclude(command, ['id', 'slug', 'created_at', 'updated_at']) ?? emptyCommand + const formCommand = exclude(command, ["id", "slug", "created_at", "updated_at"]) ?? emptyCommand return (
@@ -105,7 +105,7 @@ const CommandForm = ({ method = 'post', command, ...props }: CommandFormProps) = - { command?.id ? 'Update' : 'Create' } Command + { command?.id ? "Update" : "Create" } Command ) diff --git a/app/frontend/Pages/Commands/Index/index.tsx b/app/frontend/Pages/Commands/Index/index.tsx index 75edab9..8c69eea 100644 --- a/app/frontend/Pages/Commands/Index/index.tsx +++ b/app/frontend/Pages/Commands/Index/index.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Routes } from '@/lib' -import { IndexPageTemplate } from '@/Features' -import { NewIcon } from '@/Components/Icons' -import CommandsTable from '../Table' -import { type Pagination } from '@/types' +import React from "react" +import { Routes } from "@/lib" +import { IndexPageTemplate } from "@/Features" +import { NewIcon } from "@/Components/Icons" +import CommandsTable from "../Table" +import { type Pagination } from "@/types" interface ICommandIndexProps { commands: Schema.CommandsIndex[] @@ -19,7 +19,7 @@ const CommandsIndex = ({ commands, pagination }: ICommandIndexProps) => { pagination={ pagination } deleteRoute={ Routes.commands() } menuOptions={ [ - { label: 'New Command', href: Routes.newCommand(), icon: NewIcon }, + { label: "New Command", href: Routes.newCommand(), icon: NewIcon }, ] } > diff --git a/app/frontend/Pages/Commands/New/index.tsx b/app/frontend/Pages/Commands/New/index.tsx index bbc88f1..657f660 100644 --- a/app/frontend/Pages/Commands/New/index.tsx +++ b/app/frontend/Pages/Commands/New/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import CommandForm from '../Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import CommandForm from "../Form" interface INewCommandProps { command: Schema.CommandsFormData } const NewCommand = ({ ...data }: INewCommandProps) => { - const title = 'New Command' + const title = "New Command" return ( diff --git a/app/frontend/Pages/Commands/Show/index.tsx b/app/frontend/Pages/Commands/Show/index.tsx index 04a0441..f061abb 100644 --- a/app/frontend/Pages/Commands/Show/index.tsx +++ b/app/frontend/Pages/Commands/Show/index.tsx @@ -1,15 +1,15 @@ -import React from 'react' -import { Box, Code, DangerousHtml, Group, Title, Link, Menu, Page, Section, List } from '@/Components' -import { Routes } from '@/lib' -import { Control } from '@/Features/Controls' -import { EditButton } from '@/Components/Button' +import React from "react" +import { Box, Code, DangerousHtml, Group, Title, Link, Menu, Page, Section, List } from "@/Components" +import { Routes } from "@/lib" +import { Control } from "@/Features/Controls" +import { EditButton } from "@/Components/Button" interface ShowCommandProps { command: Schema.CommandsShow } const ShowCommand = ({ command }: ShowCommandProps) => { - const title = command.title ?? 'Command' + const title = command.title ?? "Command" return ( diff --git a/app/frontend/Pages/Commands/Table.tsx b/app/frontend/Pages/Commands/Table.tsx index 1a1fe8a..0f4b9c1 100644 --- a/app/frontend/Pages/Commands/Table.tsx +++ b/app/frontend/Pages/Commands/Table.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Table, Link, type TableProps } from '@/Components' -import { DeleteButton, EditButton } from '@/Components/Button' +import React from "react" +import { Routes } from "@/lib" +import { Table, Link, type TableProps } from "@/Components" +import { DeleteButton, EditButton } from "@/Components/Button" const CommandTable = (props: TableProps) => { return ( diff --git a/app/frontend/Pages/Devise/Confirmations/New/index.tsx b/app/frontend/Pages/Devise/Confirmations/New/index.tsx index cbec9d0..08655a6 100644 --- a/app/frontend/Pages/Devise/Confirmations/New/index.tsx +++ b/app/frontend/Pages/Devise/Confirmations/New/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Form, TextInput, Submit } from '@/Components/Form' -import { Routes } from '@/lib' -import { Title, Link } from '@/Components' +import React from "react" +import { Form, TextInput, Submit } from "@/Components/Form" +import { Routes } from "@/lib" +import { Title, Link } from "@/Components" interface IConfirmationsNew { user: Schema.User diff --git a/app/frontend/Pages/Devise/Login/Login.css.ts b/app/frontend/Pages/Devise/Login/Login.css.ts index 2f3a7e3..9f87979 100644 --- a/app/frontend/Pages/Devise/Login/Login.css.ts +++ b/app/frontend/Pages/Devise/Login/Login.css.ts @@ -1,6 +1,6 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' -import { rem } from '@mantine/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" +import { rem } from "@mantine/core" export const form = css` .field { diff --git a/app/frontend/Pages/Devise/Login/index.tsx b/app/frontend/Pages/Devise/Login/index.tsx index d2ad117..bfc3c3e 100644 --- a/app/frontend/Pages/Devise/Login/index.tsx +++ b/app/frontend/Pages/Devise/Login/index.tsx @@ -1,9 +1,9 @@ -import React, { useRef } from 'react' -import { Form, Field, TextInput, PasswordInput, Checkbox, Submit } from '@/Components/Form' -import { Routes } from '@/lib' -import { Title, Link } from '@/Components' -import { type UseFormProps } from 'use-inertia-form' -import * as classes from './Login.css' +import React, { useRef } from "react" +import { Form, Field, TextInput, PasswordInput, Checkbox, Submit } from "@/Components/Form" +import { Routes } from "@/lib" +import { Title, Link } from "@/Components" +import { type UseFormProps } from "use-inertia-form" +import * as classes from "./Login.css" type LoginFormData = { user: { @@ -15,15 +15,15 @@ type LoginFormData = { const defaultData = { user: { - email: '', - password: '', + email: "", + password: "", remember_me: false, }, } const Login = () => { const handleSubmit = ({ data }: UseFormProps) => { - if(data.user.email === '' || data.user.password === '') { + if(data.user.email === "" || data.user.password === "") { return false } } diff --git a/app/frontend/Pages/Devise/Passwords/New/index.tsx b/app/frontend/Pages/Devise/Passwords/New/index.tsx index e447eb5..4c13ee2 100644 --- a/app/frontend/Pages/Devise/Passwords/New/index.tsx +++ b/app/frontend/Pages/Devise/Passwords/New/index.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Field, Form, TextInput, Submit } from '@/Components/Form' -import { Routes } from '@/lib' -import { Title, Link } from '@/Components' +import React from "react" +import { Field, Form, TextInput, Submit } from "@/Components/Form" +import { Routes } from "@/lib" +import { Title, Link } from "@/Components" type TPasswordsNewFormData = { email: string @@ -9,7 +9,7 @@ type TPasswordsNewFormData = { const PasswordsNew = () => { const defaultData: TPasswordsNewFormData = { - email: '', + email: "", } return ( diff --git a/app/frontend/Pages/Devise/Register/index.tsx b/app/frontend/Pages/Devise/Register/index.tsx index aca55ba..8577a23 100644 --- a/app/frontend/Pages/Devise/Register/index.tsx +++ b/app/frontend/Pages/Devise/Register/index.tsx @@ -1,8 +1,8 @@ -import React from 'react' -import { Form, TextInput, PasswordInput, Submit, Field } from '@/Components/Form' -import { Routes } from '@/lib' -import { Box, Title, Link, Grid, Text } from '@/Components' -import { type UseFormProps } from 'use-inertia-form' +import React from "react" +import { Form, TextInput, PasswordInput, Submit, Field } from "@/Components/Form" +import { Routes } from "@/lib" +import { Box, Title, Link, Grid, Text } from "@/Components" +import { type UseFormProps } from "use-inertia-form" type RegisterFormData = { user: { @@ -21,32 +21,32 @@ const Register = ({ user, first_run }: RegisterProps) => { const handleFormChange = ({ data }: UseFormProps) => { } - const handlePasswordChange = (value: string|number, { data, getError, clearErrors }: UseFormProps) => { - if(getError('user.password') || getError('user.password_confirmation')) { + const handlePasswordChange = (value: string | number, { data, getError, clearErrors }: UseFormProps) => { + if(getError("user.password") || getError("user.password_confirmation")) { if(data.user.password === data.user.password_confirmation) { - clearErrors('user.password') - clearErrors('user.password_confirmation') + clearErrors("user.password") + clearErrors("user.password_confirmation") } } } const handleSubmit = ({ data, setError, errors, transform }: UseFormProps) => { if(data.user.password !== data.user.password_confirmation) { - setError('user.password_confirmation', 'Passwords must match') + setError("user.password_confirmation", "Passwords must match") return false } } - const handleEmailBlur = (value: string|number, form: UseFormProps) => { + const handleEmailBlur = (value: string | number, form: UseFormProps) => { } return (
{ return ( diff --git a/app/frontend/Pages/Protocols/Edit/index.tsx b/app/frontend/Pages/Protocols/Edit/index.tsx index d416111..ed7778f 100644 --- a/app/frontend/Pages/Protocols/Edit/index.tsx +++ b/app/frontend/Pages/Protocols/Edit/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import ProtocolsForm from '../Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import ProtocolsForm from "../Form" interface IEditProtocolProps { protocol: Schema.ProtocolsEdit } const EditProtocol = ({ protocol }: IEditProtocolProps) => { - const title = `Edit Protocol ${protocol?.title ? `: ${protocol.title}` : ''}` + const title = `Edit Protocol ${protocol?.title ? `: ${protocol.title}` : ""}` return ( diff --git a/app/frontend/Pages/Protocols/Form/CommandInputs.tsx b/app/frontend/Pages/Protocols/Form/CommandInputs.tsx index 53d2f05..3ec75cd 100644 --- a/app/frontend/Pages/Protocols/Form/CommandInputs.tsx +++ b/app/frontend/Pages/Protocols/Form/CommandInputs.tsx @@ -1,11 +1,11 @@ -import React, { useMemo } from 'react' -import { Grid, Label } from '@/Components' -import { NumberInput, TextInput, useDynamicInputContext } from '@/Components/Form' -import { CommandDropdown, CommandValueDropdown } from '@/Components/Dropdowns' -import { useForm } from 'use-inertia-form' -import dayjs from 'dayjs' -import { humanizeDuration } from '@/lib/formatters' -import TextInputComponent from '@/Components/Inputs/TextInput' +import React, { useMemo } from "react" +import { Grid, Label } from "@/Components" +import { NumberInput, TextInput, useDynamicInputContext } from "@/Components/Form" +import { CommandDropdown, CommandValueDropdown } from "@/Components/Dropdowns" +import { useForm } from "use-inertia-form" +import dayjs from "dayjs" +import { humanizeDuration } from "@/lib/formatters" +import TextInputComponent from "@/Components/Inputs/TextInput" interface CommandInputsProps { commands: Schema.CommandsOptions[] @@ -16,7 +16,7 @@ const CommandInputs = ({ commands }: CommandInputsProps) => { const { setData } = useForm() const handleChange = () => { - setData(`protocol.protocols_commands[${index}].command_value_id`, '') + setData(`protocol.protocols_commands[${index}].command_value_id`, "") } const activeCommand = useMemo( @@ -53,9 +53,9 @@ const CommandInputs = ({ commands }: CommandInputsProps) => { diff --git a/app/frontend/Pages/Protocols/Form/SortableFormSection.tsx b/app/frontend/Pages/Protocols/Form/SortableFormSection.tsx index d2ef1a5..5ed46b7 100644 --- a/app/frontend/Pages/Protocols/Form/SortableFormSection.tsx +++ b/app/frontend/Pages/Protocols/Form/SortableFormSection.tsx @@ -1,20 +1,20 @@ -import React, { useMemo, useState } from 'react' +import React, { useMemo, useState } from "react" import { DndContext, useSensor, useSensors, -} from '@dnd-kit/core' -import type { Active, DragEndEvent, DragStartEvent, UniqueIdentifier } from '@dnd-kit/core' +} from "@dnd-kit/core" +import type { Active, DragEndEvent, DragStartEvent, UniqueIdentifier } from "@dnd-kit/core" import { SortableContext, arrayMove, verticalListSortingStrategy, -} from '@dnd-kit/sortable' +} from "@dnd-kit/sortable" // import * as classes from './SortableList.css' -import { useForm } from 'use-inertia-form' -import { createContext } from '@/lib/hooks' -import { FormPointerSensor } from '@/Components/Sortable' +import { useForm } from "use-inertia-form" +import { createContext } from "@/lib/hooks" +import { FormPointerSensor } from "@/Components/Sortable" const [useSortableFormContext, SortableFormContextProvider] = createContext() export { useSortableFormContext } @@ -32,7 +32,7 @@ interface SortableFormSectionProps { const SortableFormSection = ({ children, model, - sortField = 'order', + sortField = "order", }: SortableFormSectionProps) => { const form = useForm() const [active, setActive] = useState(null) diff --git a/app/frontend/Pages/Protocols/Form/index.tsx b/app/frontend/Pages/Protocols/Form/index.tsx index 456e3d2..3579091 100644 --- a/app/frontend/Pages/Protocols/Form/index.tsx +++ b/app/frontend/Pages/Protocols/Form/index.tsx @@ -1,11 +1,11 @@ -import React from 'react' -import { Form, TextInput, Submit, RichText } from '@/Components/Form' -import { UseInertiaFormProps, type HTTPVerb, type UseFormProps } from 'use-inertia-form' -import { Grid } from '@/Components' -import CommandInputs from './CommandInputs' -import { useGetCommands } from '@/queries' -import SortableDynamicInputs from '@/Components/Form/Components/DynamicInputs/SortableDynamicInputs' -import { exclude } from '@/lib' +import React from "react" +import { Form, TextInput, Submit, RichText } from "@/Components/Form" +import { UseInertiaFormProps, type HTTPVerb, type UseFormProps } from "use-inertia-form" +import { Grid } from "@/Components" +import CommandInputs from "./CommandInputs" +import { useGetCommands } from "@/queries" +import SortableDynamicInputs from "@/Components/Form/Components/DynamicInputs/SortableDynamicInputs" +import { exclude } from "@/lib" type ProtocolFormData = { protocol: Schema.ProtocolsFormData @@ -14,11 +14,11 @@ type ProtocolFormData = { export interface IProtocolFormProps { to: string method?: HTTPVerb - onSubmit?: (object: UseFormProps) => boolean|void + onSubmit?: (object: UseFormProps) => boolean | void protocol: Schema.ProtocolsFormData } -const ProtocolForm = ({ method = 'post', protocol, ...props }: IProtocolFormProps) => { +const ProtocolForm = ({ method = "post", protocol, ...props }: IProtocolFormProps) => { const { data: commands } = useGetCommands({ initialData: protocol.commands as Schema.CommandsEdit[], }) @@ -28,13 +28,13 @@ const ProtocolForm = ({ method = 'post', protocol, ...props }: IProtocolFormProp data.protocol.protocols_commands = data.protocol.protocols_commands.map(cmd => { const excludeKeys = []; - (['value', 'delay'] as const).forEach(check => { - if(cmd[check] === '') { + (["value", "delay"] as const).forEach(check => { + if(cmd[check] === "") { excludeKeys.push(check) } }) - return exclude(cmd, 'value') + return exclude(cmd, "value") }) return data @@ -44,13 +44,13 @@ const ProtocolForm = ({ method = 'post', protocol, ...props }: IProtocolFormProp return ( - { protocol.id ? 'Update' : 'Create' } Protocol + { protocol.id ? "Update" : "Create" } Protocol ) diff --git a/app/frontend/Pages/Protocols/Form/protocolFormData.ts b/app/frontend/Pages/Protocols/Form/protocolFormData.ts index acd42ff..06533a5 100644 --- a/app/frontend/Pages/Protocols/Form/protocolFormData.ts +++ b/app/frontend/Pages/Protocols/Form/protocolFormData.ts @@ -1,11 +1,11 @@ -import { exclude } from '@/lib' +import { exclude } from "@/lib" export const transformProtocolFormData = (protocol: Schema.ProtocolsFormData) => { return exclude(protocol, [ - 'id', - 'slug', - 'created_at', - 'updated_at', + "id", + "slug", + "created_at", + "updated_at", // 'commands[].slug', // 'commands[].address', // 'commands[].description', diff --git a/app/frontend/Pages/Protocols/Index/index.tsx b/app/frontend/Pages/Protocols/Index/index.tsx index 599fc82..8e6f656 100644 --- a/app/frontend/Pages/Protocols/Index/index.tsx +++ b/app/frontend/Pages/Protocols/Index/index.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Routes } from '@/lib' -import { IndexPageTemplate } from '@/Features' -import { NewIcon } from '@/Components/Icons' -import ProtocolsTable from '../Table' -import { type Pagination } from '@/types' +import React from "react" +import { Routes } from "@/lib" +import { IndexPageTemplate } from "@/Features" +import { NewIcon } from "@/Components/Icons" +import ProtocolsTable from "../Table" +import { type Pagination } from "@/types" interface IProtocolIndexProps { protocols: Schema.ProtocolsIndex[] @@ -19,7 +19,7 @@ const ProtocolsIndex = ({ protocols, pagination }: IProtocolIndexProps) => { pagination={ pagination } deleteRoute={ Routes.protocols() } menuOptions={ [ - { label: 'New Protocol', href: Routes.newProtocol(), icon: NewIcon }, + { label: "New Protocol", href: Routes.newProtocol(), icon: NewIcon }, ] } > diff --git a/app/frontend/Pages/Protocols/New/index.tsx b/app/frontend/Pages/Protocols/New/index.tsx index b5d4da6..5f26716 100644 --- a/app/frontend/Pages/Protocols/New/index.tsx +++ b/app/frontend/Pages/Protocols/New/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import ProtocolForm from '../Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import ProtocolForm from "../Form" interface INewProtocolProps { protocol: Schema.ProtocolsFormData } const NewProtocol = ({ ...data }: INewProtocolProps) => { - const title = 'New Protocol' + const title = "New Protocol" return ( diff --git a/app/frontend/Pages/Protocols/Show/index.tsx b/app/frontend/Pages/Protocols/Show/index.tsx index 6d59687..8ef5fed 100644 --- a/app/frontend/Pages/Protocols/Show/index.tsx +++ b/app/frontend/Pages/Protocols/Show/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Box, Code, DangerousHtml, Group, Title, Menu, Page, Section, Table, Link } from '@/Components' -import { Routes } from '@/lib' -import { EditButton } from '@/Components/Button' +import React from "react" +import { Box, Code, DangerousHtml, Group, Title, Menu, Page, Section, Table, Link } from "@/Components" +import { Routes } from "@/lib" +import { EditButton } from "@/Components/Button" interface IShowProtocolProps { protocol: Schema.ProtocolsShow } const ShowProtocol = ({ protocol }: IShowProtocolProps) => { - const title = protocol.title || 'Protocol' + const title = protocol.title || "Protocol" return ( diff --git a/app/frontend/Pages/Protocols/Table.tsx b/app/frontend/Pages/Protocols/Table.tsx index 63eec81..99a1401 100644 --- a/app/frontend/Pages/Protocols/Table.tsx +++ b/app/frontend/Pages/Protocols/Table.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Table, Link, type TableProps } from '@/Components' -import { DeleteButton, EditButton } from '@/Components/Button' +import React from "react" +import { Routes } from "@/lib" +import { Table, Link, type TableProps } from "@/Components" +import { DeleteButton, EditButton } from "@/Components/Button" const ProtocolTable = (props: TableProps) => { return ( diff --git a/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx index e878a12..d0fcf27 100644 --- a/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react" import { DndContext, useSensors, @@ -7,13 +7,13 @@ import { closestCenter, UniqueIdentifier, type DragEndEvent, -} from '@dnd-kit/core' +} from "@dnd-kit/core" import { arrayMove, SortableContext, -} from '@dnd-kit/sortable' -import { useDynamicInputs, useForm } from 'use-inertia-form' -import { Control } from '@/Features/Controls' +} from "@dnd-kit/sortable" +import { useDynamicInputs, useForm } from "use-inertia-form" +import { Control } from "@/Features/Controls" interface DndEditControlsInterfaceProps { screen: Schema.ScreensEdit @@ -21,18 +21,18 @@ interface DndEditControlsInterfaceProps { const DndEditControlsInterface = ({ screen }: DndEditControlsInterfaceProps) => { const { addInput, removeInput, paths } = useDynamicInputs({ - model: 'controls', + model: "controls", emptyData: { - title: '', - control_type: '', + title: "", + control_type: "", order: 0, min_value: 0, max_value: 0, - value: '', + value: "", screen_id: screen.id, - protocol_id: '', - command_id: '', - color: '', + protocol_id: "", + command_id: "", + color: "", }, }) diff --git a/app/frontend/Pages/Screens/Edit/Form/index.tsx b/app/frontend/Pages/Screens/Edit/Form/index.tsx index 304bfbd..d12084e 100644 --- a/app/frontend/Pages/Screens/Edit/Form/index.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/index.tsx @@ -1,8 +1,8 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Divider, Flex } from '@/Components' -import { Form, Submit } from '@/Components/Form' -import DndEditControlsInterface from './DndEditControlsInterface' +import React from "react" +import { Routes } from "@/lib" +import { Divider, Flex } from "@/Components" +import { Form, Submit } from "@/Components/Form" +import DndEditControlsInterface from "./DndEditControlsInterface" interface EditScreenFormProps { screen: Schema.ScreensEdit @@ -15,7 +15,7 @@ const EditScreenForm = ({ screen }: EditScreenFormProps) => { data={ { screen: screen } } to={ Routes.screen(screen.slug) } method="patch" - filter={ ['screen.id', 'screen.slug', 'screen.created_at', 'screen.updated_at'] } + filter={ ["screen.id", "screen.slug", "screen.created_at", "screen.updated_at"] } remember={ false } > diff --git a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx index ea7725b..f533ef5 100644 --- a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx +++ b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import { Affix, Button, Menu } from '@/Components' -import { modals } from '@mantine/modals' -import { Routes } from '@/lib' -import { useCreateControl } from '@/queries' -import { ControlForm } from '@/Features/Controls' +import React from "react" +import { Affix, Button, Menu } from "@/Components" +import { modals } from "@mantine/modals" +import { Routes } from "@/lib" +import { useCreateControl } from "@/queries" +import { ControlForm } from "@/Features/Controls" -const controlFormFilter = ['control.id', 'control.command', 'control.updated_at', 'control.created_at', 'control.command_id', 'control.protocol'] +const controlFormFilter = ["control.id", "control.command", "control.updated_at", "control.created_at", "control.command_id", "control.protocol"] -type ControlType = 'button' | 'spacer' | 'slider' +type ControlType = "button" | "spacer" | "slider" interface NewControlMenuProps { screenId: number | false @@ -23,15 +23,15 @@ const NewControlMenu = ({ screenId, menuId }: NewControlMenuProps) => { screen_id: menuId, control_type: type, order: NaN, - title: '', + title: "", }) const handleNewButtonClick = () => { modals.open({ - title: 'Add New Control Button', + title: "Add New Control Button", children: ( modals.closeAll() } diff --git a/app/frontend/Pages/Screens/Edit/ScreenControl.css.ts b/app/frontend/Pages/Screens/Edit/ScreenControl.css.ts index afac298..471f7c4 100644 --- a/app/frontend/Pages/Screens/Edit/ScreenControl.css.ts +++ b/app/frontend/Pages/Screens/Edit/ScreenControl.css.ts @@ -1,5 +1,5 @@ -import { vars } from '@/lib/theme' -import { css } from '@linaria/core' +import { vars } from "@/lib/theme" +import { css } from "@linaria/core" export const droppable = css` width: 100%; diff --git a/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx b/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx index 01a01de..8dc6a8a 100644 --- a/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx +++ b/app/frontend/Pages/Screens/Edit/ScreenTabControls/EditScreenTabButton.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Accordion, Box, Divider, Text } from '@/Components' -import { EditIcon } from '@/Components/Icons' -import { modals } from '@mantine/modals' -import ScreenForm from '../../New/Form' -import { DeleteButton } from '@/Components/Button' +import React from "react" +import { Routes } from "@/lib" +import { Accordion, Box, Divider, Text } from "@/Components" +import { EditIcon } from "@/Components/Icons" +import { modals } from "@mantine/modals" +import ScreenForm from "../../New/Form" +import { DeleteButton } from "@/Components/Button" interface EditScreenTabButtonProps { screen: Schema.ScreensOptions @@ -18,7 +18,7 @@ const EditScreenTabButton = ({ screen, onSuccess, ...props }: EditScreenTabButto e.preventDefault() modals.open({ - title: 'Edit Screen', + title: "Edit Screen", children: ( <> { const handleNewScreenModalTrigger = () => { modals.open({ - title: 'Create a New Screen', + title: "Create a New Screen", children: ( { const [currentTabId, setCurrentTabId] = useState(getScreenId(paths[1])) - const title = 'Edit Screen' + const title = "Edit Screen" const droppable = useDroppable({ - id: 'screen_droppable', + id: "screen_droppable", }) const handleTabChange = (value: string | null) => { diff --git a/app/frontend/Pages/Screens/New/Form.tsx b/app/frontend/Pages/Screens/New/Form.tsx index d83558e..7fc4659 100644 --- a/app/frontend/Pages/Screens/New/Form.tsx +++ b/app/frontend/Pages/Screens/New/Form.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Form, TextInput, Submit } from '@/Components/Form' -import { Grid } from '@/Components' -import { type HTTPVerb, type UseFormProps } from 'use-inertia-form' +import React from "react" +import { Form, TextInput, Submit } from "@/Components/Form" +import { Grid } from "@/Components" +import { type HTTPVerb, type UseFormProps } from "use-inertia-form" type TScreenFormData = { screen: Schema.ScreensFormData @@ -14,7 +14,7 @@ export interface ScreenFormProps { screen?: Schema.ScreensFormData } -const ScreenForm = ({ method = 'post', screen, ...props }: ScreenFormProps) => { +const ScreenForm = ({ method = "post", screen, ...props }: ScreenFormProps) => { return (
{ - { screen?.id ? 'Update' : 'Create' } Screen + { screen?.id ? "Update" : "Create" } Screen
diff --git a/app/frontend/Pages/Screens/New/index.tsx b/app/frontend/Pages/Screens/New/index.tsx index 1c76a20..1b5b7b6 100644 --- a/app/frontend/Pages/Screens/New/index.tsx +++ b/app/frontend/Pages/Screens/New/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import NewScreenForm from './Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import NewScreenForm from "./Form" interface INewScreenProps { screen: Schema.ScreensFormData } const NewScreen = ({ ...data }: INewScreenProps) => { - const title = 'New Screen' + const title = "New Screen" return ( diff --git a/app/frontend/Pages/Screens/Show/index.tsx b/app/frontend/Pages/Screens/Show/index.tsx index 581b4a9..68a00da 100644 --- a/app/frontend/Pages/Screens/Show/index.tsx +++ b/app/frontend/Pages/Screens/Show/index.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Flex, Page, Tabs } from '@/Components' -import Control from '../../../Features/Controls/Control' -import { Routes } from '@/lib' -import { useLocation } from '@/lib/hooks' -import { router } from '@inertiajs/react' +import React from "react" +import { Flex, Page, Tabs } from "@/Components" +import Control from "../../../Features/Controls/Control" +import { Routes } from "@/lib" +import { useLocation } from "@/lib/hooks" +import { router } from "@inertiajs/react" interface IShowScreenProps { screen: Schema.ScreensShow @@ -13,7 +13,7 @@ interface IShowScreenProps { const ShowScreen = ({ screen, screens }: IShowScreenProps) => { const { paths } = useLocation() - const title = 'Screen' + const title = "Screen" return ( diff --git a/app/frontend/Pages/Servers/Edit/index.tsx b/app/frontend/Pages/Servers/Edit/index.tsx index ffb082a..55c0068 100644 --- a/app/frontend/Pages/Servers/Edit/index.tsx +++ b/app/frontend/Pages/Servers/Edit/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import ServersForm from '../Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import ServersForm from "../Form" interface IEditServerProps { server: Schema.ServersEdit } const EditServer = ({ server }: IEditServerProps) => { - const title = 'Edit Server' + const title = "Edit Server" return ( diff --git a/app/frontend/Pages/Servers/Form.tsx b/app/frontend/Pages/Servers/Form.tsx index 1375242..53c506d 100644 --- a/app/frontend/Pages/Servers/Form.tsx +++ b/app/frontend/Pages/Servers/Form.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Grid } from '@/Components' -import { Form, TextInput, Submit, Textarea, NumberInput } from '@/Components/Form' -import { type HTTPVerb, type UseFormProps } from 'use-inertia-form' +import React from "react" +import { Grid } from "@/Components" +import { Form, TextInput, Submit, Textarea, NumberInput } from "@/Components/Form" +import { type HTTPVerb, type UseFormProps } from "use-inertia-form" type TServerFormData = { server: Schema.ServersFormData @@ -10,11 +10,11 @@ type TServerFormData = { export interface IServerFormProps { to: string method?: HTTPVerb - onSubmit?: (object: UseFormProps) => boolean|void + onSubmit?: (object: UseFormProps) => boolean | void server: Schema.ServersFormData } -const ServerForm = ({ method = 'post', server, ...props }: IServerFormProps) => { +const ServerForm = ({ method = "post", server, ...props }: IServerFormProps) => { return (
- { server.id ? 'Update' : 'Create' } Server + { server.id ? "Update" : "Create" } Server diff --git a/app/frontend/Pages/Servers/Index/index.tsx b/app/frontend/Pages/Servers/Index/index.tsx index bf9493f..7dc2ca6 100644 --- a/app/frontend/Pages/Servers/Index/index.tsx +++ b/app/frontend/Pages/Servers/Index/index.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Routes } from '@/lib' -import { IndexPageTemplate } from '@/Features' -import { NewIcon } from '@/Components/Icons' -import ServersTable from '../Table' -import { type Pagination } from '@/types' +import React from "react" +import { Routes } from "@/lib" +import { IndexPageTemplate } from "@/Features" +import { NewIcon } from "@/Components/Icons" +import ServersTable from "../Table" +import { type Pagination } from "@/types" interface IServerIndexProps { servers: Schema.ServersIndex[] @@ -19,7 +19,7 @@ const ServersIndex = ({ servers, pagination }: IServerIndexProps) => { pagination={ pagination } deleteRoute={ Routes.servers() } menuOptions={ [ - { label: 'New Server', href: Routes.newServer(), icon: NewIcon }, + { label: "New Server", href: Routes.newServer(), icon: NewIcon }, ] } > diff --git a/app/frontend/Pages/Servers/New/index.tsx b/app/frontend/Pages/Servers/New/index.tsx index ab2d1f3..2cdf945 100644 --- a/app/frontend/Pages/Servers/New/index.tsx +++ b/app/frontend/Pages/Servers/New/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Title, Page, Section } from '@/Components' -import { Routes } from '@/lib' -import ServerForm from '../Form' +import React from "react" +import { Title, Page, Section } from "@/Components" +import { Routes } from "@/lib" +import ServerForm from "../Form" interface INewServerProps { server: Schema.ServersFormData } const NewServer = ({ ...data }: INewServerProps) => { - const title = 'New Server' + const title = "New Server" return ( diff --git a/app/frontend/Pages/Servers/Show/index.tsx b/app/frontend/Pages/Servers/Show/index.tsx index 22f3ad1..153cfd3 100644 --- a/app/frontend/Pages/Servers/Show/index.tsx +++ b/app/frontend/Pages/Servers/Show/index.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import { Group, Title, Menu, Page, Section, Text } from '@/Components' -import { Routes } from '@/lib' +import React from "react" +import { Group, Title, Menu, Page, Section, Text } from "@/Components" +import { Routes } from "@/lib" interface IShowServerProps { server: Schema.ServersShow } const ShowServer = ({ server }: IShowServerProps) => { - const title = 'Server' + const title = "Server" return ( @@ -25,7 +25,7 @@ const ShowServer = ({ server }: IShowServerProps) => { - { `${server.hostname}${server?.port ? `:${server.port}` : ''}` } + { `${server.hostname}${server?.port ? `:${server.port}` : ""}` } { server?.description }
diff --git a/app/frontend/Pages/Servers/Table.tsx b/app/frontend/Pages/Servers/Table.tsx index 527e81b..cfbef26 100644 --- a/app/frontend/Pages/Servers/Table.tsx +++ b/app/frontend/Pages/Servers/Table.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Table, Link, type TableProps } from '@/Components' -import { EditButton } from '@/Components/Button' +import React from "react" +import { Routes } from "@/lib" +import { Table, Link, type TableProps } from "@/Components" +import { EditButton } from "@/Components/Button" const ServerTable = (props: TableProps) => { return ( diff --git a/app/frontend/Pages/Settings/Appearance/Index/index.tsx b/app/frontend/Pages/Settings/Appearance/Index/index.tsx index 3e356ad..b395949 100644 --- a/app/frontend/Pages/Settings/Appearance/Index/index.tsx +++ b/app/frontend/Pages/Settings/Appearance/Index/index.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useRef } from 'react' -import { Box, Title } from '@/Components' -import SettingsLayout from '../../SettingsLayout' -import useLayoutStore from '@/lib/store/LayoutStore' +import React, { useEffect, useRef } from "react" +import { Box, Title } from "@/Components" +import SettingsLayout from "../../SettingsLayout" +import useLayoutStore from "@/lib/store/LayoutStore" interface IAppearanceSettingsProps { settings: { diff --git a/app/frontend/Pages/Settings/General/Index/index.tsx b/app/frontend/Pages/Settings/General/Index/index.tsx index 740e29e..067ae3f 100644 --- a/app/frontend/Pages/Settings/General/Index/index.tsx +++ b/app/frontend/Pages/Settings/General/Index/index.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import SettingsLayout from '../../SettingsLayout' -import { Title } from '@/Components' +import React from "react" +import SettingsLayout from "../../SettingsLayout" +import { Title } from "@/Components" const General = () => { return ( diff --git a/app/frontend/Pages/Settings/Localization/Index/index.tsx b/app/frontend/Pages/Settings/Localization/Index/index.tsx index 8a0dc71..7d7fd18 100644 --- a/app/frontend/Pages/Settings/Localization/Index/index.tsx +++ b/app/frontend/Pages/Settings/Localization/Index/index.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import SettingsLayout from '../../SettingsLayout' -import { Title } from '@/Components' +import React from "react" +import SettingsLayout from "../../SettingsLayout" +import { Title } from "@/Components" const LocalizationSettings = () => { return ( diff --git a/app/frontend/Pages/Settings/Notifications/Index/index.tsx b/app/frontend/Pages/Settings/Notifications/Index/index.tsx index bd1635a..fd8e285 100644 --- a/app/frontend/Pages/Settings/Notifications/Index/index.tsx +++ b/app/frontend/Pages/Settings/Notifications/Index/index.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import SettingsLayout from '../../SettingsLayout' -import { Title } from '@/Components' +import React from "react" +import SettingsLayout from "../../SettingsLayout" +import { Title } from "@/Components" const Appearance = () => { return ( diff --git a/app/frontend/Pages/Settings/SettingsLayout.tsx b/app/frontend/Pages/Settings/SettingsLayout.tsx index 68628e3..f1107e2 100644 --- a/app/frontend/Pages/Settings/SettingsLayout.tsx +++ b/app/frontend/Pages/Settings/SettingsLayout.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react' -import { Paper, Page, Box, Section, Tabs } from '@/Components' -import { router } from '@inertiajs/react' -import { px, useMantineTheme } from '@mantine/core' -import { useViewportSize, useLocation } from '@/lib/hooks' +import React, { useEffect, useState } from "react" +import { Paper, Page, Box, Section, Tabs } from "@/Components" +import { router } from "@inertiajs/react" +import { px, useMantineTheme } from "@mantine/core" +import { useViewportSize, useLocation } from "@/lib/hooks" interface SettingsLayoutProps { children: React.ReactNode @@ -15,21 +15,21 @@ type Tab = { } const tabs: Tab[] = [ - { name: 'general', label: 'General' }, - { name: 'appearance', label: 'Appearance' }, - { name: 'mail', label: 'Mail' }, - { name: 'notifications', label: 'Notifications' }, + { name: "general", label: "General" }, + { name: "appearance", label: "Appearance" }, + { name: "mail", label: "Mail" }, + { name: "notifications", label: "Notifications" }, // { name: 'integrations', label: 'Integrations' }, - { name: 'asset_tags', label: 'Asset Tags' }, + { name: "asset_tags", label: "Asset Tags" }, // { name: 'barcodes', label: 'Barcodes' }, - { name: 'ldap', label: 'LDAP' }, - { name: 'tickets', label: 'Tickets' }, - { name: 'backups', label: 'Backups' }, - { name: 'logs', label: 'Logs' }, + { name: "ldap", label: "LDAP" }, + { name: "tickets", label: "Tickets" }, + { name: "backups", label: "Backups" }, + { name: "logs", label: "Logs" }, ] const SettingsLayout = ({ children }: SettingsLayoutProps) => { - const title = 'Settings' + const title = "Settings" const { width } = useViewportSize() const theme = useMantineTheme() const [mobileFormat, setMobileFormat] = useState(window.innerWidth < Number(px(theme.breakpoints.sm))) @@ -41,15 +41,15 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => { setMobileFormat(width < Number(px(theme.breakpoints.sm))) }, [width]) - const handleTabChange = (value: string|null) => { - router.get(`/settings/${value ?? 'general'}`, {}, { preserveState: true }) + const handleTabChange = (value: string | null) => { + router.get(`/settings/${value ?? "general"}`, {}, { preserveState: true }) } return (
{ > { tabs.map(tab => ( @@ -73,8 +75,8 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => { { tabs.map(tab => ( - - + + { children } diff --git a/app/frontend/Pages/Users/Edit/index.tsx b/app/frontend/Pages/Users/Edit/index.tsx index 054cddf..1014a53 100644 --- a/app/frontend/Pages/Users/Edit/index.tsx +++ b/app/frontend/Pages/Users/Edit/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react" const Edit = () => { return ( diff --git a/app/frontend/Pages/Users/Form.tsx b/app/frontend/Pages/Users/Form.tsx index 97ea9c0..13fc9fd 100644 --- a/app/frontend/Pages/Users/Form.tsx +++ b/app/frontend/Pages/Users/Form.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import React from "react" import { Form, TextInput, Submit, -} from '@/Components/Form' -import { type HTTPVerb, type UseFormProps } from 'use-inertia-form' +} from "@/Components/Form" +import { type HTTPVerb, type UseFormProps } from "use-inertia-form" type TUserFormData = { user: Schema.UsersFormData @@ -13,11 +13,11 @@ type TUserFormData = { export interface IUserFormProps { to: string method?: HTTPVerb - onSubmit?: (object: UseFormProps) => boolean|void + onSubmit?: (object: UseFormProps) => boolean | void user: Schema.UsersFormData } -const UserForm = ({ to, method = 'post', onSubmit, user }: IUserFormProps) => { +const UserForm = ({ to, method = "post", onSubmit, user }: IUserFormProps) => { return ( { - { user.id ? 'Update' : 'Create' } User + { user.id ? "Update" : "Create" } User ) diff --git a/app/frontend/Pages/Users/Index/index.tsx b/app/frontend/Pages/Users/Index/index.tsx index 3d4f4d3..0a81974 100644 --- a/app/frontend/Pages/Users/Index/index.tsx +++ b/app/frontend/Pages/Users/Index/index.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Routes } from '@/lib' -import { IndexPageTemplate } from '@/Features' -import { NewIcon } from '@/Components/Icons' -import UsersTable from '../Table' -import { type Pagination } from '@/types' +import React from "react" +import { Routes } from "@/lib" +import { IndexPageTemplate } from "@/Features" +import { NewIcon } from "@/Components/Icons" +import UsersTable from "../Table" +import { type Pagination } from "@/types" interface IUserIndexProps { users: Schema.UsersIndex[] @@ -19,7 +19,7 @@ const UserIndex = ({ users, pagination }: IUserIndexProps) => { pagination={ pagination } deleteRoute={ Routes.users() } menuOptions={ [ - { label: 'Invite New User', href: Routes.newUser(), icon: NewIcon }, + { label: "Invite New User", href: Routes.newUser(), icon: NewIcon }, ] } > diff --git a/app/frontend/Pages/Users/New/index.tsx b/app/frontend/Pages/Users/New/index.tsx index 0f21b57..e62427e 100644 --- a/app/frontend/Pages/Users/New/index.tsx +++ b/app/frontend/Pages/Users/New/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react" const New = () => { return ( diff --git a/app/frontend/Pages/Users/Show/index.tsx b/app/frontend/Pages/Users/Show/index.tsx index 8ddfdd7..55ee537 100644 --- a/app/frontend/Pages/Users/Show/index.tsx +++ b/app/frontend/Pages/Users/Show/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react" interface IShowUserProps { user: Schema.User diff --git a/app/frontend/Pages/Users/Table.tsx b/app/frontend/Pages/Users/Table.tsx index 42e9dce..5276754 100644 --- a/app/frontend/Pages/Users/Table.tsx +++ b/app/frontend/Pages/Users/Table.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import { Routes } from '@/lib' -import { Link, Table, type TableProps } from '@/Components' -import { EditButton } from '@/Components/Button' +import React from "react" +import { Routes } from "@/lib" +import { Link, Table, type TableProps } from "@/Components" +import { EditButton } from "@/Components/Button" const UsersTable = (props: TableProps) => { return ( @@ -9,7 +9,7 @@ const UsersTable = (props: TableProps) => { Email - Actions + Actions diff --git a/app/frontend/entrypoints/application.tsx b/app/frontend/entrypoints/application.tsx index e002294..a96a048 100644 --- a/app/frontend/entrypoints/application.tsx +++ b/app/frontend/entrypoints/application.tsx @@ -1,44 +1,51 @@ -import React from 'react' -import { createInertiaApp } from '@inertiajs/react' -import { createRoot } from 'react-dom/client' -import { PublicLayout, AppLayout, AuthLayout } from '../Layouts' - -type PagesObject = { default: React.ComponentType & { - layout?: React.ComponentType +import React from "react" +import { createInertiaApp, router } from "@inertiajs/react" +import { createRoot } from "react-dom/client" +import { LAYOUTS } from "../Layouts" +import { + applyPropsMiddleware, + setupCSRFToken, + setupInertiaListeners, + handlePageLayout, +} from "./middleware" + +import dayjs from "dayjs" +import localizedFormat from "dayjs/plugin/localizedFormat" +import duration from "dayjs/plugin/duration" +import relativeTime from "dayjs/plugin/relativeTime" + +const pages = import.meta.glob("../Pages/**/index.tsx") + +dayjs.extend(localizedFormat) +dayjs.extend(localizedFormat) +dayjs.extend(duration) +dayjs.extend(relativeTime) + +const SITE_TITLE = "OSC" + +export type PagesObject = { default: React.ComponentType & { + layout?: React.ComponentType + defaultLayout?: keyof typeof LAYOUTS } } -const pages = import.meta.glob('../Pages/**/index.tsx') +document.addEventListener("DOMContentLoaded", () => { + setupCSRFToken() + setupInertiaListeners(router) -document.addEventListener('DOMContentLoaded', () => { createInertiaApp({ - title: title => `OSC - ${title}`, - - resolve: async name => { - let checkedName = name - let layout - - switch(name.substring(0, name.indexOf('/'))) { - case 'Public': - layout = PublicLayout - checkedName = name.replace('Public/', '') - break - case 'Auth': - layout = AuthLayout - checkedName = name.replace('Auth/', '') - break - default: - layout = AppLayout - } - - const page = (await pages[`../Pages/${checkedName}/index.tsx`]()).default - - if(page.layout === undefined) page.layout = layout - - return page + title: title => `${SITE_TITLE} - ${title}`, + + resolve: async (name) => { + const page: PagesObject = (await pages[`../Pages/${name}/index.tsx`]()) + + return handlePageLayout(page) }, setup({ el, App, props }) { const root = createRoot(el) + + props.initialPage.props = applyPropsMiddleware(props.initialPage.props) + root.render() }, }) diff --git a/app/frontend/entrypoints/middleware/convertDates.ts b/app/frontend/entrypoints/middleware/convertDates.ts new file mode 100644 index 0000000..0620129 --- /dev/null +++ b/app/frontend/entrypoints/middleware/convertDates.ts @@ -0,0 +1,27 @@ +import { isPlainObject } from "lodash" + +/** + * Recursively check each prop value and convert ISO strings to dates + */ +export function convertDates | Record[]>(obj: T): T { + if(Array.isArray(obj)) { + // Recurse over array values + return obj.map(convertDates) as unknown as T + } else if(isPlainObject(obj)) { + // Recurse over object values + return Object.keys(obj).reduce((acc, key) => { + (acc as any)[key] = convertDates((obj as any)[key]) + return acc + }, {} as T) + } else if(typeof obj === "string" && isISODateString(obj)) { + // Case to convert the date object + return new Date(obj) as unknown as T + } + + return obj +} + +function isISODateString(value: string) { + const isoDateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?([+-]\d{2}:\d{2}|Z)?$/ + return isoDateFormat.test(value) +} diff --git a/app/frontend/entrypoints/middleware/handlePageLayout.tsx b/app/frontend/entrypoints/middleware/handlePageLayout.tsx new file mode 100644 index 0000000..f944299 --- /dev/null +++ b/app/frontend/entrypoints/middleware/handlePageLayout.tsx @@ -0,0 +1,23 @@ +import { + LAYOUTS, + AuthLayout, + PublicLayout, + AppLayout, + LayoutProps, +} from "@/Layouts" +import { PagesObject } from "../application" + +const LAYOUT_COMPONENTS: Record React.JSX.Element> = { + "auth": AuthLayout, + "app": AppLayout, + "public": PublicLayout, +} as const + +const handlePageLayout = (page: PagesObject) => { + const DefaultLayout = LAYOUT_COMPONENTS[page.default.defaultLayout as keyof typeof LAYOUTS] || AppLayout + page.default.layout ||= (children: React.ReactNode) => { children } + + return page.default +} + +export default handlePageLayout diff --git a/app/frontend/entrypoints/middleware/index.ts b/app/frontend/entrypoints/middleware/index.ts new file mode 100644 index 0000000..a815d39 --- /dev/null +++ b/app/frontend/entrypoints/middleware/index.ts @@ -0,0 +1,49 @@ +import { ErrorBag, Errors, PageProps, type Router } from "@inertiajs/core" +import { convertDates } from "./convertDates" +import axios from "axios" + +export { default as handlePageLayout } from "./handlePageLayout" + +export const applyPropsMiddleware = (props: PageProps & { + errors: Errors & ErrorBag +}) => { + return convertDates(props) +} + +export function setupCSRFToken() { + const csrfToken = (document.querySelector("meta[name=csrf-token]") as HTMLMetaElement).content + axios.defaults.headers.common["X-CSRF-Token"] = csrfToken +} + +export function setupInertiaListeners(router: Router) { + // Handle both full and partial page updates + router.on("navigate", (event) => { + event.detail.page.props = applyPropsMiddleware(event.detail.page.props) + }) + + // Handle partial updates + const originalReload = router.reload.bind(router) + router.reload = function(options?: { only?: string[] }) { + if(options?.only) { + // Intercept the response to apply middleware to partial updates + const originalVisit = router.visit.bind(router) + router.visit = function(url, visitOptions = {}) { + const originalPreserveState = visitOptions.preserveState + visitOptions.preserveState = (page) => { + if(typeof originalPreserveState === "function") { + page.props = applyPropsMiddleware(page.props) + return originalPreserveState(page) + } + page.props = applyPropsMiddleware(page.props) + return true + } + return originalVisit(url, visitOptions) + } + + const result = originalReload(options) + router.visit = originalVisit + return result + } + return originalReload(options) + } +} diff --git a/app/frontend/lib/Collections/NestedObject.ts b/app/frontend/lib/Collections/NestedObject.ts index a5242c6..874f0d8 100644 --- a/app/frontend/lib/Collections/NestedObject.ts +++ b/app/frontend/lib/Collections/NestedObject.ts @@ -1,9 +1,9 @@ -import { unset, get, set, isEmpty } from 'lodash' +import { unset, get, set, isEmpty } from "lodash" export default class NestedObject { data = {} - constructor(initialData?: Record|Map) { + constructor(initialData?: Record | Map) { if(!initialData) return for(const [key, value] of Object.entries(initialData)) { diff --git a/app/frontend/lib/Collections/NestedURLSearchParams.ts b/app/frontend/lib/Collections/NestedURLSearchParams.ts index 2bca9cf..aaf7241 100644 --- a/app/frontend/lib/Collections/NestedURLSearchParams.ts +++ b/app/frontend/lib/Collections/NestedURLSearchParams.ts @@ -1,12 +1,12 @@ -import { unset, get, set, isEmpty } from 'lodash' +import { unset, get, set, isEmpty } from "lodash" export default class NestedURLSearchParams { _data: Record = {} - constructor(initialData?: string|Record|URLSearchParams) { + constructor(initialData?: string | Record | URLSearchParams) { if(!initialData) return - if(initialData instanceof URLSearchParams || typeof initialData === 'string') { + if(initialData instanceof URLSearchParams || typeof initialData === "string") { let searchParams: URLSearchParams if(initialData instanceof URLSearchParams) { @@ -65,7 +65,7 @@ export default class NestedURLSearchParams { } toString() { - return `?${convertToQueryString(this.data)}`.replace(/\&$/, '') + return `?${convertToQueryString(this.data)}`.replace(/\&$/, "") } params() { @@ -77,10 +77,10 @@ export default class NestedURLSearchParams { } } -const ignoreValues = [undefined, null, ''] +const ignoreValues = [undefined, null, ""] -const convertToQueryString = (obj: Record, parentKey = ''): string => { - let queryString = '' +const convertToQueryString = (obj: Record, parentKey = ""): string => { + let queryString = "" for(const key in obj) { if(Object.prototype.hasOwnProperty.call(obj, key)) { @@ -90,7 +90,7 @@ const convertToQueryString = (obj: Record, parentKey = ''): string const formattedKey = parentKey ? `${parentKey}[${key}]` : key - if(typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]' && value !== null) { + if(typeof value === "object" && Object.prototype.toString.call(value) === "[object Object]" && value !== null) { queryString += convertToQueryString(value, formattedKey) } else { let formattedValue = value diff --git a/app/frontend/lib/collections.test.ts b/app/frontend/lib/collections.test.ts index 22cbb90..8eae41d 100644 --- a/app/frontend/lib/collections.test.ts +++ b/app/frontend/lib/collections.test.ts @@ -1,22 +1,22 @@ -import { describe, expect, test } from 'vitest' -import { coerceArray, exclude, findMax } from './collections' +import { describe, expect, test } from "vitest" +import { coerceArray, exclude, findMax } from "./collections" const records = [ - { id: 1, str: 'hello1', obj: { key: 12 } }, - { id: 2, str: 'hello2', obj: { key: 11 } }, - { id: 3, str: 'hello3', obj: { key: 10 } }, + { id: 1, str: "hello1", obj: { key: 12 } }, + { id: 2, str: "hello2", obj: { key: 11 } }, + { id: 3, str: "hello3", obj: { key: 10 } }, ] -describe('findMax', () => { - test('returns element with largest id', () => { - expect(findMax(records, 'id')).toEqual(records[2]) +describe("findMax", () => { + test("returns element with largest id", () => { + expect(findMax(records, "id")).toEqual(records[2]) }) - test('handles strings', () => { - expect(findMax(records, 'str')).toBeNaN() + test("handles strings", () => { + expect(findMax(records, "str")).toBeNaN() }) - test('handles nested objects', () => { - expect(findMax(records, 'obj.key')).toEqual(records[0]) + test("handles nested objects", () => { + expect(findMax(records, "obj.key")).toEqual(records[0]) }) }) diff --git a/app/frontend/lib/collections.ts b/app/frontend/lib/collections.ts index c7905c7..5216d95 100644 --- a/app/frontend/lib/collections.ts +++ b/app/frontend/lib/collections.ts @@ -1,7 +1,7 @@ -import { cloneDeep, get, unset } from 'lodash' +import { cloneDeep, get, unset } from "lodash" -export { default as NestedObject } from './Collections/NestedObject' -export { default as NestedURLSearchParams } from './Collections/NestedURLSearchParams' +export { default as NestedObject } from "./Collections/NestedObject" +export { default as NestedURLSearchParams } from "./Collections/NestedURLSearchParams" /** * Ensures the object is an array member diff --git a/app/frontend/lib/colors.ts b/app/frontend/lib/colors.ts new file mode 100644 index 0000000..455aaf0 --- /dev/null +++ b/app/frontend/lib/colors.ts @@ -0,0 +1,28 @@ +import { isLightColor, MantinePrimaryShade, MantineTheme } from "@mantine/core" + +export const calculateContrastingColor = ( + color: string, + theme: MantineTheme, + colorScheme: "light" | "dark" +) => { + const black = theme?.black || "#000000" + const white = theme?.white || "#FFFFFF" + + if(color === "black") return white + if(color === "white" || color === undefined) return black + + let validatedColor = color + if(validatedColor.charAt(0) !== "#") { + if(!theme || !colorScheme) return black + + const colors = theme.colors + const primaryShade = theme.primaryShade as MantinePrimaryShade + + if(Object.keys(colors).includes(color)) { + const shade = primaryShade[colorScheme] + validatedColor = colors[color][shade] + } + } + + return isLightColor(validatedColor) ? black : white +} diff --git a/app/frontend/lib/formatters.ts b/app/frontend/lib/formatters.ts index 22ab2b4..890eb4c 100644 --- a/app/frontend/lib/formatters.ts +++ b/app/frontend/lib/formatters.ts @@ -1,30 +1,30 @@ -import dayjs from 'dayjs' +import dayjs from "dayjs" -export const currency = (amount: number, currency = 'USD') => { - const formatter = new Intl.NumberFormat('en-US', { - style: 'currency', +export const currency = (amount: number, currency = "USD") => { + const formatter = new Intl.NumberFormat("en-US", { + style: "currency", currency, }) return formatter.format(amount) } export const date = { - short: (date: string|Date) => dayjs(new Date(date)).format('MM/DD/YYYY'), - long: (date: string|Date) => dayjs(new Date(date)).format('MM/DD/YYYY HH:mm:ss'), - relative: (date: string|Date) => { - return dayjs(new Date(date)).format('MM/DD/YYYY') + short: (date: string | Date) => dayjs(new Date(date)).format("MM/DD/YYYY"), + long: (date: string | Date) => dayjs(new Date(date)).format("MM/DD/YYYY HH:mm:ss"), + relative: (date: string | Date) => { + return dayjs(new Date(date)).format("MM/DD/YYYY") }, - english: (date: string|Date) => dayjs(new Date(date)).format('MM/DD/YYYY'), + english: (date: string | Date) => dayjs(new Date(date)).format("MM/DD/YYYY"), } -const durationKeys = ['year', 'month', 'day', 'hour', 'minute', 'second', 'millisecond'] as const +const durationKeys = ["year", "month", "day", "hour", "minute", "second", "millisecond"] as const export const humanizeDuration = (duration: plugin.Duration) => { - let humanDuration = '' + let humanDuration = "" durationKeys.forEach((unit) => { const value = duration.get(unit) if(value > 0) { - humanDuration += `${humanDuration === '' ? '' : ', '}${value} ${unit}${value > 1 ? 's' : ''}` + humanDuration += `${humanDuration === "" ? "" : ", "}${value} ${unit}${value > 1 ? "s" : ""}` } }) diff --git a/app/frontend/lib/forms.ts b/app/frontend/lib/forms.ts index 674d0e4..e2b74e9 100644 --- a/app/frontend/lib/forms.ts +++ b/app/frontend/lib/forms.ts @@ -1,23 +1,23 @@ -import { useForm } from 'use-inertia-form' +import { useForm } from "use-inertia-form" /** * Test if a value is "unset" in the context of a form input value. Returns true if the value can be considered "empty" * @param v Variable to test whether is an "unset" value * @returns boolean */ -export const isUnset = (v: T): v is T extends (null | undefined | '') ? T : never => { +export const isUnset = (v: T): v is T extends (null | undefined | "") ? T : never => { if( v === null || v === undefined || - (typeof v === 'string' && v === '') || + (typeof v === "string" && v === "") || Number.isNaN(v) || - JSON.stringify(v) === '{}' + JSON.stringify(v) === "{}" ) { return true } if(Array.isArray(v)) { - return !v.some(el => el !== '' && el !== undefined) + return !v.some(el => el !== "" && el !== undefined) } return false @@ -29,13 +29,13 @@ export function getInputOnChange( return (val: Value | React.ChangeEvent | ((current: Value) => Value)) => { if(!val) { setValue(val as Value) - } else if(typeof val === 'function') { + } else if(typeof val === "function") { setValue(val) - } else if(typeof val === 'object' && 'nativeEvent' in val) { + } else if(typeof val === "object" && "nativeEvent" in val) { const { currentTarget } = val if(currentTarget instanceof HTMLInputElement) { - if(currentTarget.type === 'checkbox') { + if(currentTarget.type === "checkbox") { setValue(currentTarget.checked as any) } else { setValue(currentTarget.value as any) diff --git a/app/frontend/lib/hooks/createContext.ts b/app/frontend/lib/hooks/createContext.ts index 2dd63d6..309ed75 100644 --- a/app/frontend/lib/hooks/createContext.ts +++ b/app/frontend/lib/hooks/createContext.ts @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react" /** * createContext @@ -15,7 +15,7 @@ const createContext = () => { function useContext(error = true) { const c = React.useContext(context) if(error && c === null) { - throw new Error('useContext must be inside a Provider with a value') + throw new Error("useContext must be inside a Provider with a value") } return c } diff --git a/app/frontend/lib/hooks/index.ts b/app/frontend/lib/hooks/index.ts index 764db18..bea827b 100644 --- a/app/frontend/lib/hooks/index.ts +++ b/app/frontend/lib/hooks/index.ts @@ -1,13 +1,13 @@ -export { default as createContext } from './createContext' -export { default as useBooleanToggle } from './useBooleanToggle' -export { default as useCheckboxState } from './useCheckboxState' -export { default as useLocation } from './useLocation' -export { default as usePageProps } from './usePageProps' -export { default as useInit } from './useInit' -export { default as useAuth } from './useAuth' -export { default as useContrastingTextColor } from './useContrastingTextColor' -export { default as useCurrency, type UseCurrencyOptions } from './useCurrency' -export { default as useViewportSize } from './useViewportSize' +export { default as createContext } from "./createContext" +export { default as useBooleanToggle } from "./useBooleanToggle" +export { default as useCheckboxState } from "./useCheckboxState" +export { default as useLocation } from "./useLocation" +export { default as usePageProps } from "./usePageProps" +export { default as useInit } from "./useInit" +export { default as useAuth } from "./useAuth" +export { default as useContrastingTextColor } from "./useContrastingTextColor" +export { default as useCurrency, type UseCurrencyOptions } from "./useCurrency" +export { default as useViewportSize } from "./useViewportSize" -export * from '@mantine/hooks' +export * from "@mantine/hooks" diff --git a/app/frontend/lib/hooks/useAuth.ts b/app/frontend/lib/hooks/useAuth.ts index a7d2349..d520650 100644 --- a/app/frontend/lib/hooks/useAuth.ts +++ b/app/frontend/lib/hooks/useAuth.ts @@ -1,4 +1,4 @@ -import usePageProps from './usePageProps' +import usePageProps from "./usePageProps" const useAuth = () => { const { auth } = usePageProps() diff --git a/app/frontend/lib/hooks/useBooleanToggle.ts b/app/frontend/lib/hooks/useBooleanToggle.ts index 4a649bc..c7af21a 100644 --- a/app/frontend/lib/hooks/useBooleanToggle.ts +++ b/app/frontend/lib/hooks/useBooleanToggle.ts @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState } from "react" const useBooleanToggle = (initial: boolean): [boolean, (explicit?: boolean) => void] => { const [value, setValue] = useState(initial) diff --git a/app/frontend/lib/hooks/useCheckboxState.ts b/app/frontend/lib/hooks/useCheckboxState.ts index 66c5040..d9061c7 100644 --- a/app/frontend/lib/hooks/useCheckboxState.ts +++ b/app/frontend/lib/hooks/useCheckboxState.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect } from "react" const useCheckboxState = (length: number, selected: number) => { const [allChecked, setAllChecked] = useState(false) diff --git a/app/frontend/lib/hooks/useColorSchemeOption.ts b/app/frontend/lib/hooks/useColorSchemeOption.ts index 2632988..71186a0 100644 --- a/app/frontend/lib/hooks/useColorSchemeOption.ts +++ b/app/frontend/lib/hooks/useColorSchemeOption.ts @@ -1,9 +1,9 @@ -import { useColorScheme } from '@mantine/hooks' +import { useColorScheme } from "@mantine/hooks" const useColorSchemeOption = (light: any, dark: any) => { const colorScheme = useColorScheme() - return colorScheme === 'dark' ? dark : light + return colorScheme === "dark" ? dark : light } export default useColorSchemeOption diff --git a/app/frontend/lib/hooks/useContrastingTextColor.ts b/app/frontend/lib/hooks/useContrastingTextColor.ts index cae4872..4384d6f 100644 --- a/app/frontend/lib/hooks/useContrastingTextColor.ts +++ b/app/frontend/lib/hooks/useContrastingTextColor.ts @@ -1,18 +1,18 @@ -import { isLightColor, type MantinePrimaryShade, useMantineColorScheme, useMantineTheme } from '@mantine/core' +import { isLightColor, type MantinePrimaryShade, useMantineColorScheme, useMantineTheme } from "@mantine/core" const useContrastingTextColor = (color: string) => { const { colors, primaryShade } = useMantineTheme() const { colorScheme } = useMantineColorScheme() - if(color === undefined) return 'black' + if(color === undefined) return "black" let validatedColor = color if(Object.keys(colors).includes(color)) { - const shade = (primaryShade as MantinePrimaryShade)[(colorScheme as 'light'|'dark')] + const shade = (primaryShade as MantinePrimaryShade)[(colorScheme as "light" | "dark")] validatedColor = colors[color][shade] } - return isLightColor(validatedColor, 0.379) ? 'black' : 'white' + return isLightColor(validatedColor, 0.379) ? "black" : "white" } export default useContrastingTextColor diff --git a/app/frontend/lib/hooks/useCurrency.ts b/app/frontend/lib/hooks/useCurrency.ts index bb90d95..77a4336 100644 --- a/app/frontend/lib/hooks/useCurrency.ts +++ b/app/frontend/lib/hooks/useCurrency.ts @@ -1,6 +1,6 @@ -import { type Money } from '@/types' +import { type Money } from "@/types" -export type UseCurrencyOptions = Omit +export type UseCurrencyOptions = Omit interface UseCurrencyProps { amount: number | Money | null @@ -9,28 +9,28 @@ interface UseCurrencyProps { options?: UseCurrencyOptions } -type OmittedOptions = Pick +type OmittedOptions = Pick const useCurrency = ({ amount, - currency = 'USD', - locale = 'en-US', + currency = "USD", + locale = "en-US", options = {}, }: UseCurrencyProps): [amount: number, formatter: Intl.NumberFormat] => { let currencyIso = currency - if(typeof amount !== 'number' && amount?.hasOwnProperty('currency_iso')) { + if(typeof amount !== "number" && amount?.hasOwnProperty("currency_iso")) { currencyIso = amount.currency_iso } let value = 0 - if(typeof amount === 'number') { + if(typeof amount === "number") { value = amount - } else if(amount?.hasOwnProperty('amount')) { + } else if(amount?.hasOwnProperty("amount")) { value = amount.amount } const baseOptions: OmittedOptions = { - style: 'currency', + style: "currency", currency: currencyIso, } diff --git a/app/frontend/lib/hooks/useInit.ts b/app/frontend/lib/hooks/useInit.ts index 5de1362..467f028 100644 --- a/app/frontend/lib/hooks/useInit.ts +++ b/app/frontend/lib/hooks/useInit.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef } from "react" const useInit = (cb: Function) => { const hasRunRef = useRef(false) diff --git a/app/frontend/lib/hooks/useLocation.ts b/app/frontend/lib/hooks/useLocation.ts index e83e095..3d73d40 100644 --- a/app/frontend/lib/hooks/useLocation.ts +++ b/app/frontend/lib/hooks/useLocation.ts @@ -1,6 +1,6 @@ -import { useState, useEffect, useMemo } from 'react' -import { omit } from 'lodash' -import { NestedURLSearchParams } from '@/lib/collections' +import { useState, useEffect, useMemo } from "react" +import { omit } from "lodash" +import { NestedURLSearchParams } from "@/lib/collections" const useLocation = () => { const [location, setLocation] = useState(window.location) @@ -10,10 +10,10 @@ const useLocation = () => { } useEffect(() => { - window.addEventListener('popstate', listenToPopstate) + window.addEventListener("popstate", listenToPopstate) return () => { - window.removeEventListener('popstate', listenToPopstate) + window.removeEventListener("popstate", listenToPopstate) } }, []) @@ -21,14 +21,14 @@ const useLocation = () => { return { ...omit(location, [ - 'toString', - 'replace', - 'reload', - 'assign', - 'ancestorOrigins', + "toString", + "replace", + "reload", + "assign", + "ancestorOrigins", ]), path: `${location.origin}${location.pathname}`, - paths: location.pathname.replace(/^\//, '').split('/'), + paths: location.pathname.replace(/^\//, "").split("/"), params, paramsAsJson: useMemo(() => { const hash: Record = {} diff --git a/app/frontend/lib/hooks/usePageProps.ts b/app/frontend/lib/hooks/usePageProps.ts index 4f6e8d6..4e29def 100644 --- a/app/frontend/lib/hooks/usePageProps.ts +++ b/app/frontend/lib/hooks/usePageProps.ts @@ -1,6 +1,6 @@ -import { usePage } from '@inertiajs/react' -import { PageProps, Errors, ErrorBag } from '@inertiajs/core' -import { type FlashMessage } from '@/types' +import { usePage } from "@inertiajs/react" +import { PageProps, Errors, ErrorBag } from "@inertiajs/core" +import { type FlashMessage } from "@/types" export interface SharedInertiaProps extends PageProps { auth: { diff --git a/app/frontend/lib/hooks/useViewportSize.ts b/app/frontend/lib/hooks/useViewportSize.ts index 0a72820..277dbe1 100644 --- a/app/frontend/lib/hooks/useViewportSize.ts +++ b/app/frontend/lib/hooks/useViewportSize.ts @@ -1,5 +1,5 @@ -import { useEffect } from 'react'; -import { useViewportSize as useMantineViewportSize } from '@mantine/hooks'; +import { useEffect } from "react" +import { useViewportSize as useMantineViewportSize } from "@mantine/hooks" type OnChangeCallback = (dimensions: { width: number, height: number }) => void diff --git a/app/frontend/lib/index.tsx b/app/frontend/lib/index.tsx index 87630f7..065a94f 100644 --- a/app/frontend/lib/index.tsx +++ b/app/frontend/lib/index.tsx @@ -1,16 +1,19 @@ -import { Routes } from '@/lib' +import { Routes } from "@/lib" -export * as Routes from './routes/routes' -export * as formatter from './formatters' +export * as Routes from "./routes/routes" +export * as formatter from "./formatters" -export * from './uuid' -export * from './strings' -export * from './collections' -export * from './forms' -export * from './theme' -export * from './units' +export * from "./colors" +export * from "./collections" +export * from "./forms" +export * from "./strings" +export * from "./theme" +export * from "./units" +export * from "./uuid" -export const polymorphicRoute = (model: string, param: string|number) => { +export const polymorphicRoute = (model: string, param: string | number) => { // @ts-ignore return Routes[camelize(model)](param) } + +export { withLayout } from "./withLayout" diff --git a/app/frontend/lib/store/LayoutStore.ts b/app/frontend/lib/store/LayoutStore.ts deleted file mode 100644 index 8afca44..0000000 --- a/app/frontend/lib/store/LayoutStore.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { create } from 'zustand' -import { type IColorSlice, type ISidebarSlice } from './slices' -import { createColorSlice } from './slices/colorSlice' -import { createSidebarSlice } from './slices/sidebarSlice' - -type LayoutStoreSlices = IColorSlice & ISidebarSlice & { - defaults: { - tableRecordsLimit: number - } -} - -const useLayoutStore = create()((...args) => ({ - defaults: { - tableRecordsLimit: 25, - }, - ...createColorSlice(...args), - ...createSidebarSlice(...args), -})) - -export default useLayoutStore diff --git a/app/frontend/lib/store/appLayoutMachine.ts b/app/frontend/lib/store/appLayoutMachine.ts index 06955f0..ae3649c 100644 --- a/app/frontend/lib/store/appLayoutMachine.ts +++ b/app/frontend/lib/store/appLayoutMachine.ts @@ -1,8 +1,8 @@ -import { createMachine } from 'xstate' +import { createMachine } from "xstate" const appLayoutMachine = createMachine({ - id: 'appLayout', - initial: '', + id: "appLayout", + initial: "", }) export default appLayoutMachine diff --git a/app/frontend/lib/store/contrastingColorSlice.ts b/app/frontend/lib/store/contrastingColorSlice.ts new file mode 100644 index 0000000..be26b12 --- /dev/null +++ b/app/frontend/lib/store/contrastingColorSlice.ts @@ -0,0 +1,38 @@ +import { StateCreator } from "zustand" +import { type MantineTheme } from "@mantine/core" +import { calculateContrastingColor } from "../colors" + +export interface ContrastingColorSlice { + contrastingColorsMap: Map + setThemeData: (theme: MantineTheme, colorScheme: "light" | "dark") => void + getContrastingColor: (color: string) => string +} + +export const createContrastingColorSlice: StateCreator = (set, get) => { + let theme: MantineTheme + let colorScheme: "light" | "dark" + + return { + theme: undefined, + colorScheme: "light", + contrastingColorsMap: new Map(), + + setThemeData: (newTheme, newColorScheme) => { + theme = newTheme + colorScheme = newColorScheme + }, + + getContrastingColor: (color) => { + const { contrastingColorsMap } = get() + + if(contrastingColorsMap.has(color)) { + return contrastingColorsMap.get(color)! + } + + const contrastingColor = calculateContrastingColor(color, theme, colorScheme) + contrastingColorsMap.set(color, contrastingColor) + + return contrastingColor + }, + } +} diff --git a/app/frontend/lib/store/defaultsSlice.ts b/app/frontend/lib/store/defaultsSlice.ts new file mode 100644 index 0000000..d073c6c --- /dev/null +++ b/app/frontend/lib/store/defaultsSlice.ts @@ -0,0 +1,13 @@ +import { StateCreator } from "zustand" + +export interface DefaultsSlice { + defaults: { + tableRecordsLimit: number + } +} + +export const createDefaultsSlice: StateCreator = () => ({ + defaults: { + tableRecordsLimit: 25, + }, +}) diff --git a/app/frontend/lib/store/index.ts b/app/frontend/lib/store/index.ts index 5c2f22f..8dde74f 100644 --- a/app/frontend/lib/store/index.ts +++ b/app/frontend/lib/store/index.ts @@ -1 +1,14 @@ -export { default as useLayoutStore } from './LayoutStore' +import { create } from "zustand" +import { createContrastingColorSlice, type ContrastingColorSlice } from "./contrastingColorSlice" +import { createLayoutSlice, type LayoutSlice } from "./layoutSlice" +import { createDefaultsSlice, type DefaultsSlice } from "./defaultsSlice" + +type GlobalStore = ContrastingColorSlice & LayoutSlice & DefaultsSlice + +const useStore = create((...args) => ({ + ...createContrastingColorSlice(...args), + ...createLayoutSlice(...args), + ...createDefaultsSlice(...args), +})) + +export default useStore diff --git a/app/frontend/lib/store/layoutSlice.tsx b/app/frontend/lib/store/layoutSlice.tsx new file mode 100644 index 0000000..d8d5eb3 --- /dev/null +++ b/app/frontend/lib/store/layoutSlice.tsx @@ -0,0 +1,50 @@ +import { StateCreator } from "zustand" +import { defaultColor } from "../theme" + +export interface LayoutSlice { + siteTitle: string + + sidebarOpen: boolean + sidebarVisible: boolean + sidebarBreakpoint: string + toggleSidebarOpen: (sidebarOpen?: boolean) => void + setSidebarVisible: (visible: boolean) => void + + primaryColor: string + setPrimaryColor: (color: string) => void + + NavMenu: React.JSX.Element + setNavMenu: (menu: React.JSX.Element) => void +} + +export const createLayoutSlice: StateCreator = (set) => ({ + siteTitle: "OSC", + + sidebarOpen: true, + sidebarVisible: false, + sidebarBreakpoint: "sm", + + primaryColor: defaultColor, + + NavMenu: <>, + + toggleSidebarOpen: sidebarOpen => set(state => { + let setValue = sidebarOpen + if(sidebarOpen === undefined) { + setValue = !state.sidebarOpen + } + return { sidebarOpen: setValue } + }), + + setSidebarVisible: visible => set(() => ({ + sidebarVisible: visible, + })), + + setPrimaryColor: color => set(state => ({ + primaryColor: color, + })), + + setNavMenu: menu => set(() => ({ + NavMenu: menu, + })), +}) diff --git a/app/frontend/lib/store/slices/colorSlice.ts b/app/frontend/lib/store/slices/colorSlice.ts deleted file mode 100644 index a28cacb..0000000 --- a/app/frontend/lib/store/slices/colorSlice.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { StateCreator } from 'zustand' -import { defaultColor } from '@/lib/theme' - -export interface IColorSlice { - primaryColor: string - setPrimaryColor: (color: string) => void -} - -export const createColorSlice: StateCreator = -(set) => ({ - primaryColor: defaultColor, - - setPrimaryColor: color => set(() => ({ - primaryColor: color, - })), - -}) diff --git a/app/frontend/lib/store/slices/index.ts b/app/frontend/lib/store/slices/index.ts deleted file mode 100644 index 2fb223d..0000000 --- a/app/frontend/lib/store/slices/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { type IColorSlice } from './colorSlice' -export { type IMenuSlice } from './menuSlice' -export { type ISidebarSlice } from './sidebarSlice' diff --git a/app/frontend/lib/store/slices/menuSlice.tsx b/app/frontend/lib/store/slices/menuSlice.tsx deleted file mode 100644 index 694b725..0000000 --- a/app/frontend/lib/store/slices/menuSlice.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { StateCreator } from 'zustand' -// import { DefaultMenu } from '@/Layouts/AppLayout/AppSidebar/menus' - -export interface IMenuSlice { - NavMenu: React.JSX.Element - setNavMenu: (menu: React.JSX.Element) => void -} - -export const createMenuSlice: StateCreator = -(set) => ({ - NavMenu: <>, // , - - setNavMenu: menu => set(() => ({ - NavMenu: menu, - })), -}) diff --git a/app/frontend/lib/store/slices/sidebarSlice.ts b/app/frontend/lib/store/slices/sidebarSlice.ts deleted file mode 100644 index ee5f296..0000000 --- a/app/frontend/lib/store/slices/sidebarSlice.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { StateCreator } from 'zustand' - -export interface ISidebarSlice { - sidebarOpen: boolean - sidebarVisible: boolean - sidebarBreakpoint: string - toggleSidebarOpen: (sidebarOpen?: boolean) => void - setSidebarVisible: (visible: boolean) => void -} - -export const createSidebarSlice: StateCreator = -(set) => ({ - sidebarOpen: true, - sidebarVisible: false, - sidebarBreakpoint: 'sm', - - toggleSidebarOpen: sidebarOpen => set(state => { - let setValue = sidebarOpen - if(sidebarOpen === undefined) { - setValue = !state.sidebarOpen - } - return { sidebarOpen: setValue } - }), - - setSidebarVisible: visible => set(() => ({ - sidebarVisible: visible, - })), -}) diff --git a/app/frontend/lib/strings.ts b/app/frontend/lib/strings.ts index a5f9e42..f20ad37 100644 --- a/app/frontend/lib/strings.ts +++ b/app/frontend/lib/strings.ts @@ -1,16 +1,16 @@ const WORDS_REGEX = /[A-Z\xC0-\xD6\xD8-\xDE]?[a-z\xDF-\xF6\xF8-\xFF]+|[A-Z\xC0-\xD6\xD8-\xDE]+(?![a-z\xDF-\xF6\xF8-\xFF])|\d+/g -const toWords = (str?: string|null) => { - const input = str ?? '' +const toWords = (str?: string | null) => { + const input = str ?? "" return input.match(WORDS_REGEX) || [] } -export const capitalize = (str?: string|null): string => { - if(typeof str !== 'string') return '' +export const capitalize = (str?: string | null): string => { + if(typeof str !== "string") return "" return str.charAt(0).toUpperCase() + str.slice(1) } -export const toCamelCase = (str?: string|null) => { +export const toCamelCase = (str?: string | null) => { const words = toWords(str) return words.map((word, i) => { @@ -19,7 +19,7 @@ export const toCamelCase = (str?: string|null) => { return lowered } return capitalize(lowered) - }).join('') + }).join("") } export const initials = (str: string) => { @@ -36,8 +36,8 @@ export const initials = (str: string) => { return initials } -export const toKebabCase = (str?: string|null) => { +export const toKebabCase = (str?: string | null) => { const words = toWords(str) - return words.map((word, i) => word.toLocaleLowerCase()).join('-') + return words.map((word, i) => word.toLocaleLowerCase()).join("-") } diff --git a/app/frontend/lib/theme.ts b/app/frontend/lib/theme.ts index e69551e..a186c3c 100644 --- a/app/frontend/lib/theme.ts +++ b/app/frontend/lib/theme.ts @@ -1,23 +1,23 @@ -import { createTheme, DEFAULT_THEME, mergeMantineTheme } from '@mantine/core' -import { themeToVars } from '@mantine/vanilla-extract' -import breakpoints from './breakpoints.mjs' +import { createTheme, DEFAULT_THEME, mergeMantineTheme } from "@mantine/core" +import { themeToVars } from "@mantine/vanilla-extract" +import breakpoints from "./breakpoints.mjs" -export const defaultColor = 'blue' +export const defaultColor = "blue" export const themeObject = { breakpoints, - defaultRadius: 'xs', + defaultRadius: "xs", spacing: { - xxl: 'calc(2.5rem * var(--mantine-scale))', - xxs: 'calc(0.5rem * var(--mantine-scale))', + xxl: "calc(2.5rem * var(--mantine-scale))", + xxs: "calc(0.5rem * var(--mantine-scale))", }, shadows: { - xs: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)', - sm: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)', - md: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)', - lg: '0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)', - xl: '0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22)', - xxl: '0 29px 52px rgba(0,0,0,0.40), 0 25px 16px rgba(0,0,0,0.20)', + xs: "0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)", + sm: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)", + md: "0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)", + lg: "0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)", + xl: "0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22)", + xxl: "0 29px 52px rgba(0,0,0,0.40), 0 25px 16px rgba(0,0,0,0.20)", }, other: { table: { diff --git a/app/frontend/lib/units.ts b/app/frontend/lib/units.ts index 57575d5..b507954 100644 --- a/app/frontend/lib/units.ts +++ b/app/frontend/lib/units.ts @@ -1,7 +1,7 @@ -import { px } from '@mantine/core' +import { px } from "@mantine/core" const fixedPx = (val: string) => { - return +px(val) + return + px(val) } export { fixedPx as px } diff --git a/app/frontend/lib/uuid.ts b/app/frontend/lib/uuid.ts index 17900e2..179b1e9 100644 --- a/app/frontend/lib/uuid.ts +++ b/app/frontend/lib/uuid.ts @@ -1,4 +1,4 @@ -const B64_SEPARATOR = ' ' +const B64_SEPARATOR = " " export const encodeId = (model: string, id: number) => btoa(`${model}${B64_SEPARATOR}${id}`) diff --git a/app/frontend/lib/withLayout.ts b/app/frontend/lib/withLayout.ts new file mode 100644 index 0000000..5ec6f96 --- /dev/null +++ b/app/frontend/lib/withLayout.ts @@ -0,0 +1,13 @@ +import { LAYOUTS } from "@/Layouts" +import { type PagesObject } from "@/entrypoints/application" + +type LayoutType = keyof typeof LAYOUTS + +export function withLayout( + Component: PagesObject["default"], + layout: LayoutType +) { + Component.defaultLayout = layout + + return Component +} diff --git a/app/frontend/queries/commands/index.ts b/app/frontend/queries/commands/index.ts index 938d84e..a494946 100644 --- a/app/frontend/queries/commands/index.ts +++ b/app/frontend/queries/commands/index.ts @@ -1,2 +1,2 @@ -export * from './queries' -export * from './mutations' +export * from "./queries" +export * from "./mutations" diff --git a/app/frontend/queries/commands/keys.ts b/app/frontend/queries/commands/keys.ts index 831c164..dcb4f7b 100644 --- a/app/frontend/queries/commands/keys.ts +++ b/app/frontend/queries/commands/keys.ts @@ -1,7 +1,7 @@ -const queryRoot = 'commands' +const queryRoot = "commands" export const queryKeys = { commands: [queryRoot] as const, command: (slug: string) => [queryRoot, slug] as const, - payloadTypes: [queryRoot, 'payloadTypes'] as const, + payloadTypes: [queryRoot, "payloadTypes"] as const, } diff --git a/app/frontend/queries/commands/mutations.ts b/app/frontend/queries/commands/mutations.ts index 0e6bd1d..d73ea0a 100644 --- a/app/frontend/queries/commands/mutations.ts +++ b/app/frontend/queries/commands/mutations.ts @@ -1,8 +1,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import { type ReactMutationFunction } from ".." -import axios from 'axios'; -import { Routes } from "@/lib"; -import { queryKeys } from "./keys"; +import axios from "axios" +import { Routes } from "@/lib" +import { queryKeys } from "./keys" export const useCreateCommand: ReactMutationFunction = (options) => { const queryClient = useQueryClient() diff --git a/app/frontend/queries/commands/queries.ts b/app/frontend/queries/commands/queries.ts index 7528042..6ba6ecc 100644 --- a/app/frontend/queries/commands/queries.ts +++ b/app/frontend/queries/commands/queries.ts @@ -1,9 +1,9 @@ -import { useQuery } from '@tanstack/react-query' -import { Routes } from '@/lib' -import axios from 'axios' -import { ComboboxData } from '@mantine/core' -import { type ReactQueryFunction } from '..' -import { queryKeys } from "./keys"; +import { useQuery } from "@tanstack/react-query" +import { Routes } from "@/lib" +import axios from "axios" +import { ComboboxData } from "@mantine/core" +import { type ReactQueryFunction } from ".." +import { queryKeys } from "./keys" export const useGetCommands: ReactQueryFunction = (options) => { return useQuery({ queryKey: queryKeys.commands, diff --git a/app/frontend/queries/controls/index.ts b/app/frontend/queries/controls/index.ts index 938d84e..a494946 100644 --- a/app/frontend/queries/controls/index.ts +++ b/app/frontend/queries/controls/index.ts @@ -1,2 +1,2 @@ -export * from './queries' -export * from './mutations' +export * from "./queries" +export * from "./mutations" diff --git a/app/frontend/queries/controls/keys.ts b/app/frontend/queries/controls/keys.ts index 22620a2..101863a 100644 --- a/app/frontend/queries/controls/keys.ts +++ b/app/frontend/queries/controls/keys.ts @@ -1,4 +1,4 @@ -const queryRoot = 'control' +const queryRoot = "control" export const queryKeys = { controls: [queryRoot] as const, diff --git a/app/frontend/queries/controls/mutations.ts b/app/frontend/queries/controls/mutations.ts index 0c17654..f2a87ab 100644 --- a/app/frontend/queries/controls/mutations.ts +++ b/app/frontend/queries/controls/mutations.ts @@ -1,8 +1,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import { type ReactMutationFunction } from ".." -import axios from 'axios'; -import { Routes } from "@/lib"; -import { queryKeys } from "./keys"; +import axios from "axios" +import { Routes } from "@/lib" +import { queryKeys } from "./keys" export const useCreateControl: ReactMutationFunction< Schema.ControlsFormData, diff --git a/app/frontend/queries/controls/queries.ts b/app/frontend/queries/controls/queries.ts index 82be53c..c530289 100644 --- a/app/frontend/queries/controls/queries.ts +++ b/app/frontend/queries/controls/queries.ts @@ -1,7 +1,7 @@ -import { useQuery } from '@tanstack/react-query' -import { Routes } from '@/lib' -import axios from 'axios' -import { type ReactQueryFunction } from '..' +import { useQuery } from "@tanstack/react-query" +import { Routes } from "@/lib" +import axios from "axios" +import { type ReactQueryFunction } from ".." export const useGetControl: ReactQueryFunction = ({ slug }, options) => { return useQuery({ @@ -16,7 +16,7 @@ export const useGetControl: ReactQueryFunction = (options) => { return useQuery({ - queryKey: ['controls'], + queryKey: ["controls"], queryFn: async () => { const res = await axios.get(Routes.apiControls()) return res.data diff --git a/app/frontend/queries/index.ts b/app/frontend/queries/index.ts index 829fed8..fbfda9f 100644 --- a/app/frontend/queries/index.ts +++ b/app/frontend/queries/index.ts @@ -3,13 +3,13 @@ import { type UseMutationOptions, type UseQueryResult, type UseMutationResult, -} from '@tanstack/react-query' +} from "@tanstack/react-query" /** * Query types */ -interface LimitedQueryOptions extends Omit, 'queryKey' | 'queryFn'> {} +interface LimitedQueryOptions extends Omit, "queryKey" | "queryFn"> {} type ReactQueryFunctionBasic = (options?: LimitedQueryOptions) => UseQueryResult @@ -30,7 +30,7 @@ export type ReactQueryFunction = */ interface LimitedMutationOptions extends - Omit, 'mutationKey' | 'onSuccess'> { + Omit, "mutationKey" | "onSuccess"> { onSuccess?: (data: TReturnData, variables: TVariables) => void } @@ -58,7 +58,7 @@ export type ReactMutationFunction< * Folder exports */ -export * from './commands' -export * from './protocols' -export * from './servers' -export * from './controls' +export * from "./commands" +export * from "./protocols" +export * from "./servers" +export * from "./controls" diff --git a/app/frontend/queries/protocols/index.ts b/app/frontend/queries/protocols/index.ts index 938d84e..a494946 100644 --- a/app/frontend/queries/protocols/index.ts +++ b/app/frontend/queries/protocols/index.ts @@ -1,2 +1,2 @@ -export * from './queries' -export * from './mutations' +export * from "./queries" +export * from "./mutations" diff --git a/app/frontend/queries/protocols/keys.ts b/app/frontend/queries/protocols/keys.ts index 7d280c8..707d35c 100644 --- a/app/frontend/queries/protocols/keys.ts +++ b/app/frontend/queries/protocols/keys.ts @@ -1,7 +1,7 @@ -const queryRoot = 'protocols' +const queryRoot = "protocols" export const queryKeys = { protocols: [queryRoot] as const, protocol: (slug: string) => [queryRoot, slug] as const, - protocolOptions: [queryRoot, 'options'] as const, + protocolOptions: [queryRoot, "options"] as const, } diff --git a/app/frontend/queries/protocols/mutations.ts b/app/frontend/queries/protocols/mutations.ts index 2babab1..c44459a 100644 --- a/app/frontend/queries/protocols/mutations.ts +++ b/app/frontend/queries/protocols/mutations.ts @@ -1,8 +1,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import { type ReactMutationFunction } from ".." -import axios from 'axios'; -import { Routes } from "@/lib"; -import { queryKeys } from "./keys"; +import axios from "axios" +import { Routes } from "@/lib" +import { queryKeys } from "./keys" export const useCreateProtocol: ReactMutationFunction< Schema.ProtocolsFormData, diff --git a/app/frontend/queries/protocols/queries.ts b/app/frontend/queries/protocols/queries.ts index d8acfd5..3333ace 100644 --- a/app/frontend/queries/protocols/queries.ts +++ b/app/frontend/queries/protocols/queries.ts @@ -1,8 +1,8 @@ -import { useQuery } from '@tanstack/react-query' -import { Routes } from '@/lib' -import axios from 'axios' -import { type ReactQueryFunction } from '..' -import { queryKeys } from './keys' +import { useQuery } from "@tanstack/react-query" +import { Routes } from "@/lib" +import axios from "axios" +import { type ReactQueryFunction } from ".." +import { queryKeys } from "./keys" export const useGetProtocol: ReactQueryFunction = ({ slug }, options) => { return useQuery({ diff --git a/app/frontend/queries/servers/index.ts b/app/frontend/queries/servers/index.ts index 938d84e..a494946 100644 --- a/app/frontend/queries/servers/index.ts +++ b/app/frontend/queries/servers/index.ts @@ -1,2 +1,2 @@ -export * from './queries' -export * from './mutations' +export * from "./queries" +export * from "./mutations" diff --git a/app/frontend/queries/servers/keys.ts b/app/frontend/queries/servers/keys.ts index 0b66fed..2a8999e 100644 --- a/app/frontend/queries/servers/keys.ts +++ b/app/frontend/queries/servers/keys.ts @@ -1,4 +1,4 @@ -const queryRoot = 'servers' +const queryRoot = "servers" export const queryKeys = { servers: [queryRoot] as const, diff --git a/app/frontend/queries/servers/mutations.ts b/app/frontend/queries/servers/mutations.ts index 3fe2401..c8c9d63 100644 --- a/app/frontend/queries/servers/mutations.ts +++ b/app/frontend/queries/servers/mutations.ts @@ -1,8 +1,8 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import { type ReactMutationFunction } from ".." -import axios from 'axios'; -import { Routes } from "@/lib"; -import { queryKeys } from "./keys"; +import axios from "axios" +import { Routes } from "@/lib" +import { queryKeys } from "./keys" export const useCreateServer: ReactMutationFunction< Schema.ServersFormData, diff --git a/app/frontend/queries/servers/queries.ts b/app/frontend/queries/servers/queries.ts index 6fefdbc..a39604a 100644 --- a/app/frontend/queries/servers/queries.ts +++ b/app/frontend/queries/servers/queries.ts @@ -1,8 +1,8 @@ -import { useQuery } from '@tanstack/react-query' -import { Routes } from '@/lib' -import axios from 'axios' -import { type ReactQueryFunction } from '..' -import { queryKeys } from './keys' +import { useQuery } from "@tanstack/react-query" +import { Routes } from "@/lib" +import axios from "axios" +import { type ReactQueryFunction } from ".." +import { queryKeys } from "./keys" export const useGetServers: ReactQueryFunction = (options) => { return useQuery({ diff --git a/app/frontend/queries/users/index.ts b/app/frontend/queries/users/index.ts index 938d84e..a494946 100644 --- a/app/frontend/queries/users/index.ts +++ b/app/frontend/queries/users/index.ts @@ -1,2 +1,2 @@ -export * from './queries' -export * from './mutations' +export * from "./queries" +export * from "./mutations" diff --git a/app/frontend/queries/users/keys.ts b/app/frontend/queries/users/keys.ts index 5f1b792..3d0a7db 100644 --- a/app/frontend/queries/users/keys.ts +++ b/app/frontend/queries/users/keys.ts @@ -1,8 +1,8 @@ -const queryRoot = 'users' +const queryRoot = "users" export const queryKeys = { users: [queryRoot] as const, user: (id: string | number) => [queryRoot, id] as const, - userPreferences: (id: string | number) => [queryRoot, id, 'preferences'] as const, - userTablePreferences: (id: string | number) => [queryRoot, id, 'table_preferences'] as const, + userPreferences: (id: string | number) => [queryRoot, id, "preferences"] as const, + userTablePreferences: (id: string | number) => [queryRoot, id, "table_preferences"] as const, } diff --git a/app/frontend/queries/users/mutations.ts b/app/frontend/queries/users/mutations.ts index 7a11367..cc80c63 100644 --- a/app/frontend/queries/users/mutations.ts +++ b/app/frontend/queries/users/mutations.ts @@ -1,9 +1,9 @@ -import { Routes } from '@/lib' -import axios from 'axios' -import { type UserPreferences, type UserTablePreferences } from '@/types' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { type ReactMutationFunction } from '..' -import { queryKeys } from './keys' +import { Routes } from "@/lib" +import axios from "axios" +import { type UserPreferences, type UserTablePreferences } from "@/types" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type ReactMutationFunction } from ".." +import { queryKeys } from "./keys" type UserPreferencesParams = { id: string | number @@ -23,8 +23,8 @@ export const useUpdateUserPreferences: ReactMutationFunction< const res = await axios.patch(Routes.apiUpdateUserPreferences(id), { user: { user_preferences: preferences }, }) - if(res.statusText !== 'OK') { - throw new Error('Failed to update user preferences') + if(res.statusText !== "OK") { + throw new Error("Failed to update user preferences") } return res.data }, @@ -54,8 +54,8 @@ export const useUpdateUserTablePreferences: ReactMutationFunction< const res = await axios.patch(Routes.apiUpdateTablePreferences(id), { user: { table_preferences: preferences }, }) - if(res.statusText !== 'OK') { - throw new Error('Failed to update table preferences') + if(res.statusText !== "OK") { + throw new Error("Failed to update table preferences") } return res.data }, diff --git a/app/frontend/queries/users/queries.ts b/app/frontend/queries/users/queries.ts index 57397d6..9ce972f 100644 --- a/app/frontend/queries/users/queries.ts +++ b/app/frontend/queries/users/queries.ts @@ -1,8 +1,8 @@ -import { useQuery } from '@tanstack/react-query' -import { Routes } from '@/lib' -import axios from 'axios' -import { type ReactQueryFunction } from '..' -import { queryKeys } from './keys' +import { useQuery } from "@tanstack/react-query" +import { Routes } from "@/lib" +import axios from "axios" +import { type ReactQueryFunction } from ".." +import { queryKeys } from "./keys" export const useGetUsers: ReactQueryFunction = (options) => { return useQuery({ diff --git a/app/frontend/types/FlashMessage.ts b/app/frontend/types/FlashMessage.ts index d158ac6..5c7c913 100644 --- a/app/frontend/types/FlashMessage.ts +++ b/app/frontend/types/FlashMessage.ts @@ -1 +1 @@ -export type FlashMessage = Record<'success' | 'alert' | 'info' | 'warning', string> +export type FlashMessage = Record<"success" | "alert" | "info" | "warning", string> diff --git a/app/frontend/types/InputTypes.ts b/app/frontend/types/InputTypes.ts index 7e69313..ff000fe 100644 --- a/app/frontend/types/InputTypes.ts +++ b/app/frontend/types/InputTypes.ts @@ -1 +1 @@ -export type InputType = 'button'|'checkbox'|'color'|'currency'|'date'|'datetime-local'|'email'|'file'|'hidden'|'image'|'month'|'number'|'password'|'radio'|'range'|'reset'|'search'|'select'|'submit'|'tel'|'text'|'textarea'|'time'|'url' +export type InputType = "button" | "checkbox" | "color" | "currency" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "select" | "submit" | "tel" | "text" | "textarea" | "time" | "url" diff --git a/app/frontend/types/Money.ts b/app/frontend/types/Money.ts index 4e2ee85..62b17f7 100644 --- a/app/frontend/types/Money.ts +++ b/app/frontend/types/Money.ts @@ -7,4 +7,4 @@ export type Money = { export type CurrencyOption = { symbol: string code: string -} \ No newline at end of file +} diff --git a/app/frontend/types/Pagination.ts b/app/frontend/types/Pagination.ts index 1d8eca3..815d979 100644 --- a/app/frontend/types/Pagination.ts +++ b/app/frontend/types/Pagination.ts @@ -1,15 +1,4 @@ -export interface Pagination { - count: number - pages: number - limit: number - current_page: number - next_page: number - prev_page: number - is_first_page: boolean - is_last_page: boolean -} - export type PaginatedModel = { data: T - pagination: Pagination -} \ No newline at end of file + pagination: Schema.Pagination +} diff --git a/app/frontend/types/UserPreferences.ts b/app/frontend/types/UserPreferences.ts index 6612b8e..abcec33 100644 --- a/app/frontend/types/UserPreferences.ts +++ b/app/frontend/types/UserPreferences.ts @@ -1,3 +1,3 @@ export interface UserPreferences { - colorScheme?: 'light'|'dark'|'auto' + colorScheme?: "light" | "dark" | "auto" } diff --git a/app/frontend/types/index.ts b/app/frontend/types/index.ts index 5af06a3..63a7037 100644 --- a/app/frontend/types/index.ts +++ b/app/frontend/types/index.ts @@ -1,6 +1,6 @@ -export { type FlashMessage } from './FlashMessage' -export { type InputType } from './InputTypes' -export { type Pagination, type PaginatedModel } from './Pagination' -export { type UserTablePreferences } from './UserTablePreferences' -export { type UserPreferences } from './UserPreferences' -export { type Money, type CurrencyOption } from './Money' +export { type FlashMessage } from "./FlashMessage" +export { type InputType } from "./InputTypes" +export { type Pagination, type PaginatedModel } from "./Pagination" +export { type UserTablePreferences } from "./UserTablePreferences" +export { type UserPreferences } from "./UserPreferences" +export { type Money, type CurrencyOption } from "./Money" diff --git a/app/frontend/types/inertia.d.ts b/app/frontend/types/inertia.d.ts new file mode 100644 index 0000000..bd5dde1 --- /dev/null +++ b/app/frontend/types/inertia.d.ts @@ -0,0 +1,48 @@ +import { type InertiaFormProps as DefaultInertiaFormProps, InertiaHeadProps } from "@inertiajs/core-react" +import { Page, PageProps, Errors, ErrorBag } from "@inertiajs/core" + +declare global { + + interface SharedInertiaProps extends PageProps { + auth: { + form_authenticty_token: string + user: Schema.UsersShare + } + flash: FlashMessage + errors: Errors & ErrorBag + } + + interface IndexedInertiaFormProps extends DefaultInertiaFormProps{ + [key: string]: any + } + + interface InertiaFormProps> extends Omit { + errors: Record + getData: (key: string) => any + unsetData: (key: string) => void + getError: (data: string) => string + } + + interface IInertiaInputProps { + name: string + model?: string + errorKey?: string + compact?: boolean + onChange?: (value: T, form: UseFormProps) => void + onBlur?: (value: T, form: UseFormProps) => void + } + + declare namespace Inertia { + type Errors = Record + + interface FormProps> extends InertiaFormProps { + model?: string + method: HTTPVerb + to?: string + getData: (key: string) => any + getError: (data: string) => string + unsetData: (key: string) => void + submit: () => Promise + } + } +} diff --git a/app/frontend/types/mantine.d.ts b/app/frontend/types/mantine.d.ts index d258d87..04eb697 100644 --- a/app/frontend/types/mantine.d.ts +++ b/app/frontend/types/mantine.d.ts @@ -1,7 +1,7 @@ -import { type SelectProps, type InputProps } from '@mantine/core' +import { type SelectProps, type InputProps } from "@mantine/core" type OverriddenColors = { - [key in keyof Colors]: key extends 'primary' + [key in keyof Colors]: key extends "primary" ? { 0: string 1: string @@ -24,7 +24,7 @@ type OverriddenColors = { : Colors[key] } -declare module '@mantine/vanilla-extract/lib/types' { +declare module "@mantine/vanilla-extract/lib/types" { type Colors = OverriddenColors interface MantineVars { @@ -32,7 +32,7 @@ declare module '@mantine/vanilla-extract/lib/types' { } } -declare module '@mantine/vanilla-extract' { +declare module "@mantine/vanilla-extract" { type Colors = OverriddenColors interface MantineVars { @@ -40,7 +40,7 @@ declare module '@mantine/vanilla-extract' { } } -declare module '@mantine/core' { +declare module "@mantine/core" { export interface MantineThemeOther { colorSchemeOption: (light: any, dark: any) => any header: { @@ -74,7 +74,7 @@ declare module '@mantine/core' { > = { as?: T } & TProps & - Omit, keyof TProps & 'as'> + Omit, keyof TProps & "as"> interface PolymorphicComponent { as: T diff --git a/app/frontend/types/types.d.ts b/app/frontend/types/types.d.ts new file mode 100644 index 0000000..e732251 --- /dev/null +++ b/app/frontend/types/types.d.ts @@ -0,0 +1,28 @@ +import React from "react" +import { type ISelectProps } from "@/Components/Inputs/Select" +import { type SelectProps, type InputProps } from "@mantine/core" + +declare global { + type HTTPVerb = "post" | "put" | "get" | "patch" | "delete" + + type InputElementType = "button" | "checkbox" | "color" | "currency" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "select" | "submit" | "tel" | "text" | "textarea" | "time" | "url" + + type FlashMessage = Record<"success" | "alert" | "info" | "warning", { + id: string + message: string + }> + + declare namespace Schema { + + interface Pagination { + count: number + pages: number + limit: number + current_page: number + next_page: number + prev_page: number + is_first_page: boolean + is_last_page: boolean + } + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 4e69b24..46b46a7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,10 +2,10 @@ import { fixupPluginRules } from '@eslint/compat' import stylistic from '@stylistic/eslint-plugin' import reactHooksPlugin from 'eslint-plugin-react-hooks' import jsxA11yPlugin from 'eslint-plugin-jsx-a11y' -import jsoncPlugin from 'eslint-plugin-jsonc' import tsParser from '@typescript-eslint/parser' +import jsoncPlugin from 'eslint-plugin-jsonc' import jsoncParser from 'jsonc-eslint-parser' -// import stylelint from 'eslint-plugin-stylelint' +import json from "@eslint/json"; const ignores = [ 'app/javascript/**/*', @@ -51,7 +51,6 @@ export default [ 'react-hooks': fixupPluginRules(reactHooksPlugin), 'jsx-a11y': jsxA11yPlugin, '@stylistic': stylistic, - // stylelint: fixupPluginRules(stylelint), }, rules: { '@stylistic/indent': ['error', 'tab', { @@ -124,9 +123,12 @@ export default [ 'eqeqeq': 'error', 'no-console': 'warn', 'eol-last': ['error', 'always'], - // 'stylelint/no-invalid': 'error', - // ...stylelint.rules, + 'semi': ['error', 'never'], ...reactHooksPlugin.configs.recommended.rules, + '@stylistic/quotes': ['error', 'double', { + avoidEscape: true, + allowTemplateLiterals: true + }], }, }, // Typescript declaration files @@ -141,15 +143,18 @@ export default [ }, // Json files { - files: ['**/*.json', '**/*.jsonc'], + files: ['**/*.json', '**/*.jsonc', '**/*.json5'], + language: "json/json", ignores, plugins: { jsonc: jsoncPlugin, + json, }, languageOptions: { parser: jsoncParser, }, rules: { + "json/no-duplicate-keys": "error", 'jsonc/indent': ['error', 2, { ignoredNodes: ['Property'] }], '@stylistic/no-multi-spaces': 'off', }, diff --git a/package.json b/package.json index 6ea264f..03dc5bb 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@commitlint/cli": "^19.6.1", "@commitlint/config-conventional": "^19.6.0", "@eslint/compat": "^1.2.6", + "@eslint/json": "^0.10.0", "@stylistic/eslint-plugin": "^3.0.1", "@tanstack/eslint-plugin-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", diff --git a/tsconfig.json b/tsconfig.json index 677bf69..0ae316d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ /* Language and Environment */ "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["esnext", "dom", "dom.iterable"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - "jsx": "react", /* Specify what JSX code is generated. */ + "jsx": "react-jsx", /* Specify what JSX code is generated. */ "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ @@ -42,7 +42,6 @@ "./node_modules/@inertiajs/core/types" ], "types": [ /* Specify type package names to be included without being referenced in a source file. */ - "node" ], // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ "resolveJsonModule": true, /* Enable importing .json files */ diff --git a/vite.config.ts b/vite.config.ts index c083f70..f9f5fe5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,49 +1,49 @@ -import { defineConfig } from 'vite' -import RubyPlugin from 'vite-plugin-ruby' -import wyw from '@wyw-in-js/vite' -import tsconfigPaths from 'vite-tsconfig-paths' -import FullReload from 'vite-plugin-full-reload' -import react from '@vitejs/plugin-react' -import path from 'path' +import { defineConfig } from "vite" +import RubyPlugin from "vite-plugin-ruby" +import wyw from "@wyw-in-js/vite" +import tsconfigPaths from "vite-tsconfig-paths" +import FullReload from "vite-plugin-full-reload" +import react from "@vitejs/plugin-react" +import path from "path" const config = defineConfig({ build: { rollupOptions: { external: [ - './app/frontend/Images/*', + "./app/frontend/Images/*", ], }, - outDir: 'public/vite-production', + outDir: "public/vite-production", }, plugins: [ tsconfigPaths(), RubyPlugin(), - FullReload(['config/routes.rb', 'app/views/**/*'], { delay: 200 }), + FullReload(["config/routes.rb", "app/views/**/*"], { delay: 200 }), react({ babel: { - plugins: ['babel-plugin-macros'], + plugins: ["babel-plugin-macros"], }, }), wyw({ - include: ['**/*.{ts,tsx}'], + include: ["**/*.{ts,tsx}"], babelOptions: { - presets: ['@babel/preset-typescript', '@babel/preset-react'], + presets: ["@babel/preset-typescript", "@babel/preset-react"], }, }), ], resolve: { - dedupe: ['axios'], + dedupe: ["axios"], alias: { - '@': path.resolve(__dirname, 'app', 'frontend'), + "@": path.resolve(__dirname, "app", "frontend"), }, }, - base: './', + base: "./", server: { fs: { strict: false, }, }, - mode: process.env.NODE_ENV || 'development', + mode: process.env.NODE_ENV || "development", }) export default config diff --git a/vitest.config.ts b/vitest.config.ts index 842c91c..44cbfeb 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,13 +1,13 @@ -import { defineConfig, mergeConfig } from 'vitest/config' -import viteConfig from './vite.config' +import { defineConfig, mergeConfig } from "vitest/config" +import viteConfig from "./vite.config" export default mergeConfig(viteConfig, defineConfig({ test: { globals: true, - environment: 'jsdom', + environment: "jsdom", coverage: { all: true, - include: ['app/frontend/**/*.test.{ts,tsx}'], + include: ["app/frontend/**/*.test.{ts,tsx}"], }, }, })) diff --git a/yarn.lock b/yarn.lock index 4b778ce..6e30030 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1167,6 +1167,18 @@ __metadata: languageName: node linkType: hard +"@eslint/json@npm:^0.10.0": + version: 0.10.0 + resolution: "@eslint/json@npm:0.10.0" + dependencies: + "@eslint/core": "npm:^0.10.0" + "@eslint/plugin-kit": "npm:^0.2.5" + "@humanwhocodes/momoa": "npm:^3.3.4" + natural-compare: "npm:^1.4.0" + checksum: 10/663db57753902dde9a8aa6b167b33a2103fe582ae85c577dadf9734417f608ef520633263160189a7b8117ed4fb019671de20725d91d7e3c1d1e0fcc23a80975 + languageName: node + linkType: hard + "@eslint/object-schema@npm:^2.1.6": version: 2.1.6 resolution: "@eslint/object-schema@npm:2.1.6" @@ -1260,6 +1272,13 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/momoa@npm:^3.3.4": + version: 3.3.6 + resolution: "@humanwhocodes/momoa@npm:3.3.6" + checksum: 10/6135f839f6647888fa1c7b8d18f85abd050e260c12af84541da41ff75e43b1781027e84e9f99f3f50293e1d7420ff78437d85620a35b5c31424faba20366c223 + languageName: node + linkType: hard + "@humanwhocodes/retry@npm:^0.3.0": version: 0.3.1 resolution: "@humanwhocodes/retry@npm:0.3.1" @@ -8535,6 +8554,7 @@ __metadata: "@dnd-kit/modifiers": "npm:^9.0.0" "@dnd-kit/sortable": "npm:^10.0.0" "@eslint/compat": "npm:^1.2.6" + "@eslint/json": "npm:^0.10.0" "@inertiajs/core": "npm:^2.0.3" "@inertiajs/react": "npm:^2.0.3" "@linaria/core": "npm:^6.2.0" From 6477db7607ed10cf708253eb252b78cd2f51c1cd Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Fri, 31 Jan 2025 18:18:48 -0800 Subject: [PATCH 13/15] feat(workin on spacer): working on spacer --- .../Features/Controls/Control/Button/Control.tsx | 1 + .../Features/Controls/Control/Button/index.tsx | 3 +-- .../Features/Controls/Control/Spacer/index.tsx | 2 +- app/frontend/Features/Controls/Controls.css.ts | 14 +++++++++----- app/frontend/Layouts/Providers/QueryProvider.tsx | 2 +- .../Screens/Edit/Form/DndEditControlsInterface.tsx | 6 +----- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/frontend/Features/Controls/Control/Button/Control.tsx b/app/frontend/Features/Controls/Control/Button/Control.tsx index bb5255e..64b12e6 100644 --- a/app/frontend/Features/Controls/Control/Button/Control.tsx +++ b/app/frontend/Features/Controls/Control/Button/Control.tsx @@ -46,6 +46,7 @@ const ControlButton = ({ [classes.lastButtonClicked]: lastButtonClicked === control.id, }]) } control={ control } + edit={ edit } { ...props } > { children } diff --git a/app/frontend/Features/Controls/Control/Button/index.tsx b/app/frontend/Features/Controls/Control/Button/index.tsx index bf2a980..acb6b8f 100644 --- a/app/frontend/Features/Controls/Control/Button/index.tsx +++ b/app/frontend/Features/Controls/Control/Button/index.tsx @@ -1,4 +1,3 @@ -import React from "react" import { type ControlButtonBaseProps } from "./Base" import EditControlButton from "./Edit" import ControlButton from "./Control" @@ -7,6 +6,6 @@ export default ({ edit, control, ...props }: ControlButtonBaseProps) => { return edit ? : - + } diff --git a/app/frontend/Features/Controls/Control/Spacer/index.tsx b/app/frontend/Features/Controls/Control/Spacer/index.tsx index f3e63c2..c315894 100644 --- a/app/frontend/Features/Controls/Control/Spacer/index.tsx +++ b/app/frontend/Features/Controls/Control/Spacer/index.tsx @@ -7,6 +7,6 @@ export default ({ edit, control, ...props }: ControlProps) => { return edit ? : - + } diff --git a/app/frontend/Features/Controls/Controls.css.ts b/app/frontend/Features/Controls/Controls.css.ts index ec8d3c4..491357b 100644 --- a/app/frontend/Features/Controls/Controls.css.ts +++ b/app/frontend/Features/Controls/Controls.css.ts @@ -4,7 +4,10 @@ import { css } from "@linaria/core" const highlightBorderPx = 4 export const controlWrapper = css` - + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: ${vars.spacing.md}; + width: 100%; ` export const lastButtonClicked = css` @@ -12,13 +15,14 @@ export const lastButtonClicked = css` margin: calc(${vars.spacing.xs} - ${highlightBorderPx}px); ` export const spacer = css` - /* border-style: dashed; - border-width: 2px; - border-color: ${vars.colors.white}; */ + border: 2px dotted ${vars.colors.gray[4]}; + border-radius: ${vars.radius.sm}; + min-height: 40px; + margin: ${vars.spacing.xs} 0; ` export const button = css` - + width: 100%; ` export const editButtonIcon = css` diff --git a/app/frontend/Layouts/Providers/QueryProvider.tsx b/app/frontend/Layouts/Providers/QueryProvider.tsx index aba3888..a96b9df 100644 --- a/app/frontend/Layouts/Providers/QueryProvider.tsx +++ b/app/frontend/Layouts/Providers/QueryProvider.tsx @@ -10,7 +10,7 @@ const queryClient = new QueryClient() const QueryProvider = ({ children }: QueryProviderProps) => { return ( - { process.env.NODE_ENV && process.env.NODE_ENV === "development" && } + { process.env.NODE_ENV && process.env.NODE_ENV === "development" && } { children } ) diff --git a/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx index d0fcf27..ed6ce7f 100644 --- a/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx @@ -1,4 +1,3 @@ -import React from "react" import { DndContext, useSensors, @@ -8,10 +7,7 @@ import { UniqueIdentifier, type DragEndEvent, } from "@dnd-kit/core" -import { - arrayMove, - SortableContext, -} from "@dnd-kit/sortable" +import { arrayMove, SortableContext } from "@dnd-kit/sortable" import { useDynamicInputs, useForm } from "use-inertia-form" import { Control } from "@/Features/Controls" From 2c2db06b855a5ae00c09b3cfd8305fb6e0451f01 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Sun, 2 Feb 2025 10:27:45 -0800 Subject: [PATCH 14/15] feat: simplifies controls component structure --- Gemfile | 5 +- Gemfile.lock | 4 + .../Features/Controls/Control/Button/Base.tsx | 24 ------ .../Features/Controls/Control/Button/Edit.tsx | 13 --- .../Controls/Control/Button/index.tsx | 11 --- .../Features/Controls/Control/Slider/Base.tsx | 18 ---- .../Controls/Control/Slider/Control.tsx | 13 --- .../Features/Controls/Control/Slider/Edit.tsx | 13 --- .../Controls/Control/Slider/index.tsx | 12 --- .../Controls/Control/Spacer/Control.tsx | 13 --- .../Features/Controls/Control/Spacer/Edit.tsx | 14 --- .../Controls/Control/Spacer/index.tsx | 12 --- .../Control.tsx => components/Button.tsx} | 23 ++--- .../Base.tsx => components/Slider.tsx} | 5 +- .../Controls/Control/components/Spacer.tsx | 15 ++++ .../Features/Controls/Control/index.tsx | 86 +++++-------------- .../Features/Controls/ControlContainer.tsx | 19 ++++ .../Features/Controls/Controls.css.ts | 41 +++------ app/frontend/Features/Controls/index.ts | 5 +- .../Edit/Form/DndEditControlsInterface.tsx | 13 ++- .../Screens/Edit/Form}/EditControlWrapper.tsx | 11 ++- .../Screens/Edit/Form/EditControls.css.ts | 39 +++++++++ .../Screens/Edit/Form/ScreenControlForm.tsx} | 16 ++-- .../Pages/Screens/Edit/Form/index.tsx | 10 ++- .../Screens/Edit/NewControlMenu/index.tsx | 2 +- app/frontend/Pages/Screens/Show/index.tsx | 7 +- app/frontend/types/serializers/Control.d.ts | 4 +- .../types/serializers/Controls/Edit.d.ts | 4 +- .../types/serializers/Controls/FormData.d.ts | 4 +- .../types/serializers/Controls/Index.d.ts | 4 +- .../types/serializers/Controls/Show.d.ts | 4 +- app/serializers/control_serializer.rb | 2 +- 32 files changed, 167 insertions(+), 299 deletions(-) delete mode 100644 app/frontend/Features/Controls/Control/Button/Base.tsx delete mode 100644 app/frontend/Features/Controls/Control/Button/Edit.tsx delete mode 100644 app/frontend/Features/Controls/Control/Button/index.tsx delete mode 100644 app/frontend/Features/Controls/Control/Slider/Base.tsx delete mode 100644 app/frontend/Features/Controls/Control/Slider/Control.tsx delete mode 100644 app/frontend/Features/Controls/Control/Slider/Edit.tsx delete mode 100644 app/frontend/Features/Controls/Control/Slider/index.tsx delete mode 100644 app/frontend/Features/Controls/Control/Spacer/Control.tsx delete mode 100644 app/frontend/Features/Controls/Control/Spacer/Edit.tsx delete mode 100644 app/frontend/Features/Controls/Control/Spacer/index.tsx rename app/frontend/Features/Controls/Control/{Button/Control.tsx => components/Button.tsx} (72%) rename app/frontend/Features/Controls/Control/{Spacer/Base.tsx => components/Slider.tsx} (59%) create mode 100644 app/frontend/Features/Controls/Control/components/Spacer.tsx create mode 100644 app/frontend/Features/Controls/ControlContainer.tsx rename app/frontend/{Features/Controls/Control => Pages/Screens/Edit/Form}/EditControlWrapper.tsx (82%) create mode 100644 app/frontend/Pages/Screens/Edit/Form/EditControls.css.ts rename app/frontend/{Features/Controls/ControlForm.tsx => Pages/Screens/Edit/Form/ScreenControlForm.tsx} (83%) diff --git a/Gemfile b/Gemfile index 29f0191..36ddfa5 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ ruby "3.3.4" gem "rails", "~> 7.1.4" gem "pg", "~> 1.1" gem "puma", "~> 6.0" +gem "overmind", "~> 2.5" # Assets gem "inertia_rails", ">= 3.1" @@ -60,6 +61,8 @@ group :development, :test do gem 'faker', :git => 'https://github.com/faker-ruby/faker.git', :branch => 'main' # Linting + gem "rubocop", "~> 1.71", require: false + gem "rubocop-factory_bot", "~> 2.26", require: false gem "rubocop-rails", ">= 2.14", require: false gem "rubocop-rspec", ">= 2.9", require: false gem "rubocop-performance", ">= 1.13", require: false @@ -98,5 +101,3 @@ group :test do gem "simplecov", ">= 0.22.0" gem "pundit-matchers", ">= 3.1" end - -gem "overmind", "~> 2.5" diff --git a/Gemfile.lock b/Gemfile.lock index 5e4cf93..190c9ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -377,6 +377,8 @@ GEM parser (>= 3.3.1.0) rubocop-daemon (0.3.2) rubocop + rubocop-factory_bot (2.26.1) + rubocop (~> 1.61) rubocop-performance (1.23.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) @@ -499,7 +501,9 @@ DEPENDENCIES rails (~> 7.1.4) rolify (~> 6.0) rspec-rails (>= 6.0.1) + rubocop (~> 1.71) rubocop-daemon (>= 0.3.2) + rubocop-factory_bot (~> 2.26) rubocop-performance (>= 1.13) rubocop-rails (>= 2.14) rubocop-rspec (>= 2.9) diff --git a/app/frontend/Features/Controls/Control/Button/Base.tsx b/app/frontend/Features/Controls/Control/Button/Base.tsx deleted file mode 100644 index f386f3d..0000000 --- a/app/frontend/Features/Controls/Control/Button/Base.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react" -import { Button } from "@/Components" -import { ElementProps, type ButtonProps } from "@mantine/core" -import { type ControlProps } from ".." -import { controlTitle } from "../lib" - -import cx from "clsx" -import * as classes from "../../Controls.css" - -export type ControlButtonBaseProps = ControlProps & ButtonProps & ElementProps<"button", keyof ButtonProps> & { } - -const ControlButtonBase = ({ children, control, disable, className, ...props }: ControlProps) => { - return ( - - ) -} - -export default ControlButtonBase diff --git a/app/frontend/Features/Controls/Control/Button/Edit.tsx b/app/frontend/Features/Controls/Control/Button/Edit.tsx deleted file mode 100644 index a13484e..0000000 --- a/app/frontend/Features/Controls/Control/Button/Edit.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import Base, { type ControlButtonBaseProps } from "./Base" - -export type EditControlButtonProps = ControlButtonBaseProps & { - edit: true - control: Schema.ControlsFormData -} - -const EditControlButton = ({ ...props }: EditControlButtonProps) => { - return -} - -export default EditControlButton diff --git a/app/frontend/Features/Controls/Control/Button/index.tsx b/app/frontend/Features/Controls/Control/Button/index.tsx deleted file mode 100644 index acb6b8f..0000000 --- a/app/frontend/Features/Controls/Control/Button/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { type ControlButtonBaseProps } from "./Base" -import EditControlButton from "./Edit" -import ControlButton from "./Control" - -export default ({ edit, control, ...props }: ControlButtonBaseProps) => { - return edit ? - - : - -} - diff --git a/app/frontend/Features/Controls/Control/Slider/Base.tsx b/app/frontend/Features/Controls/Control/Slider/Base.tsx deleted file mode 100644 index 6a6212e..0000000 --- a/app/frontend/Features/Controls/Control/Slider/Base.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react" -import { Slider } from "@/Components" -import { ControlProps } from ".." -import { controlRoute, controlTitle } from "../lib" - -export type ControlSliderBaseProps = ControlProps & {} - -const SliderControl = ({ control, ...props }: ControlSliderBaseProps) => { - const route = controlRoute(control) - - return ( - - { controlTitle(control) } - - ) -} - -export default SliderControl diff --git a/app/frontend/Features/Controls/Control/Slider/Control.tsx b/app/frontend/Features/Controls/Control/Slider/Control.tsx deleted file mode 100644 index 4de64b4..0000000 --- a/app/frontend/Features/Controls/Control/Slider/Control.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import Base, { type ControlSliderBaseProps } from "./Base" - -type ControlSliderProps = ControlSliderBaseProps & { - edit?: false - control: Schema.ControlsShow -} - -const ControlSlider = ({ ...props }: ControlSliderProps) => { - return -} - -export default ControlSlider diff --git a/app/frontend/Features/Controls/Control/Slider/Edit.tsx b/app/frontend/Features/Controls/Control/Slider/Edit.tsx deleted file mode 100644 index 6b3e8a8..0000000 --- a/app/frontend/Features/Controls/Control/Slider/Edit.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import Base, { type ControlSliderBaseProps } from "./Base" - -type EditControlSliderProps = ControlSliderBaseProps & { - edit: true - control: Schema.ControlsFormData -} - -const EditControlSlider = ({ ...props }: EditControlSliderProps) => { - return -} - -export default EditControlSlider diff --git a/app/frontend/Features/Controls/Control/Slider/index.tsx b/app/frontend/Features/Controls/Control/Slider/index.tsx deleted file mode 100644 index 32ea631..0000000 --- a/app/frontend/Features/Controls/Control/Slider/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react" -import EditControlSlider from "./Edit" -import ControlSlider from "./Control" -import { type ControlProps } from ".." - -export default ({ edit, control, ...props }: ControlProps) => { - return edit ? - - : - -} - diff --git a/app/frontend/Features/Controls/Control/Spacer/Control.tsx b/app/frontend/Features/Controls/Control/Spacer/Control.tsx deleted file mode 100644 index 826a7e5..0000000 --- a/app/frontend/Features/Controls/Control/Spacer/Control.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import Base, { type ControlSpacerBaseProps } from "./Base" - -type ControlSpacerProps = ControlSpacerBaseProps & { - edit?: false - control: Schema.ControlsShow -} - -const ControlSpacer = ({ ...props }: ControlSpacerProps) => { - return -} - -export default ControlSpacer diff --git a/app/frontend/Features/Controls/Control/Spacer/Edit.tsx b/app/frontend/Features/Controls/Control/Spacer/Edit.tsx deleted file mode 100644 index 5e58a44..0000000 --- a/app/frontend/Features/Controls/Control/Spacer/Edit.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react" -import Base from "./Base" -import { type ControlProps } from ".." - -type EditControlSpacerProps = ControlProps & { - edit: true - control: Schema.ControlsFormData -} - -const EditControlSpacer = ({ ...props }: EditControlSpacerProps) => { - return -} - -export default EditControlSpacer diff --git a/app/frontend/Features/Controls/Control/Spacer/index.tsx b/app/frontend/Features/Controls/Control/Spacer/index.tsx deleted file mode 100644 index c315894..0000000 --- a/app/frontend/Features/Controls/Control/Spacer/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react" -import EditControlSpacer from "./Edit" -import ControlSpacer from "./Control" -import { type ControlProps } from ".." - -export default ({ edit, control, ...props }: ControlProps) => { - return edit ? - - : - -} - diff --git a/app/frontend/Features/Controls/Control/Button/Control.tsx b/app/frontend/Features/Controls/Control/components/Button.tsx similarity index 72% rename from app/frontend/Features/Controls/Control/Button/Control.tsx rename to app/frontend/Features/Controls/Control/components/Button.tsx index 64b12e6..4986138 100644 --- a/app/frontend/Features/Controls/Control/Button/Control.tsx +++ b/app/frontend/Features/Controls/Control/components/Button.tsx @@ -1,25 +1,19 @@ import React from "react" import axios from "axios" +import { Button } from "@/Components" import { useLocalStorage } from "@/lib/hooks" import { controlRoute } from "../lib" -import Base, { type ControlButtonBaseProps } from "./Base" +import { type ControlProps } from ".." import cx from "clsx" import * as classes from "../../Controls.css" -type ControlButtonProps = ControlButtonBaseProps & { - edit?: false - control: Schema.ControlsShow -} - const ControlButton = ({ - children, - edit = false, control, disable, className, ...props -}: ControlButtonProps) => { +}: ControlProps) => { const [lastButtonClicked, setLastButtonClicked] = useLocalStorage({ key: "last-button-clicked", defaultValue: undefined, @@ -40,17 +34,16 @@ const ControlButton = ({ } return ( - - { children } - + { control.title } + ) } diff --git a/app/frontend/Features/Controls/Control/Spacer/Base.tsx b/app/frontend/Features/Controls/Control/components/Slider.tsx similarity index 59% rename from app/frontend/Features/Controls/Control/Spacer/Base.tsx rename to app/frontend/Features/Controls/Control/components/Slider.tsx index 6e5f28a..7ee70c8 100644 --- a/app/frontend/Features/Controls/Control/Spacer/Base.tsx +++ b/app/frontend/Features/Controls/Control/components/Slider.tsx @@ -3,15 +3,14 @@ import { Box } from "@/Components" import { type ControlProps } from ".." import cx from "clsx" -import * as classes from "../../Controls.css" export type ControlSpacerBaseProps = ControlProps & {} -const ControlSpacerBase = ({ className, ...props }: ControlSpacerBaseProps) => { +const ControlSpacerBase = ({ control, className, ...props }: ControlSpacerBaseProps) => { return ( ) } diff --git a/app/frontend/Features/Controls/Control/components/Spacer.tsx b/app/frontend/Features/Controls/Control/components/Spacer.tsx new file mode 100644 index 0000000..7caaa42 --- /dev/null +++ b/app/frontend/Features/Controls/Control/components/Spacer.tsx @@ -0,0 +1,15 @@ +import { Box } from "@/Components" +import { type ControlProps } from ".." + +import cx from "clsx" + +const ControlSpacerBase = ({ control, className, ...props }: ControlProps) => { + return ( + + ) +} + +export default ControlSpacerBase diff --git a/app/frontend/Features/Controls/Control/index.tsx b/app/frontend/Features/Controls/Control/index.tsx index 09fbe07..3a07c7d 100644 --- a/app/frontend/Features/Controls/Control/index.tsx +++ b/app/frontend/Features/Controls/Control/index.tsx @@ -1,83 +1,37 @@ import React from "react" -import ButtonControl from "./Button" -import SliderControl from "./Slider" -import SpacerControl from "./Spacer" -import { type BoxProps } from "@mantine/core" -import { ConditionalWrapper, Box } from "@/Components" -import EditControlWrapper from "./EditControlWrapper" +import ButtonControl from "./components/Button" +import SliderControl from "./components/Slider" +import SpacerControl from "./components/Spacer" import cx from "clsx" -import * as classes from "../Controls.css" -export interface ControlPropsBase extends BoxProps { - children?: React.ReactNode - edit?: boolean - wrapper?: boolean +export interface ControlProps { + control: Schema.ControlsFormData | Schema.ControlsShow disable?: boolean + className?: string + onClick?: (e: React.MouseEvent) => void } -type ControlPropsEdit = ControlPropsBase & { - edit: true - control: Schema.ControlsFormData -} - -type ControlPropsShow = ControlPropsBase & { - edit: false | undefined - control: Schema.ControlsShow -} - -type ControlComponent = (props: Omit & { - edit: boolean +type ControlComponent = (props: ControlProps & { control: Schema.ControlsFormData | Schema.ControlsShow }) => React.ReactNode -export type ControlProps = ControlPropsEdit | ControlPropsShow - -const Control = ({ control, edit = false, wrapper = true, className, ...props }: ControlProps) => { - let ControlComponent: ControlComponent - - switch(control.control_type) { - case "button": - ControlComponent = ButtonControl as ControlComponent - break - - case "slider": - ControlComponent = SliderControl as ControlComponent - break - +const CONTROL_COMPONENTS: Record = { + button: ButtonControl, + slider: SliderControl, + spacer: SpacerControl, +} as const - case "spacer": - ControlComponent = SpacerControl as ControlComponent - break - default: - ControlComponent = React.Fragment - } +const Control = ({ control, className, ...props }: ControlProps) => { + const ControlComponent = CONTROL_COMPONENTS[control.control_type] || React.Fragment return ( - { children } } - > - ( - - { children } - - ) } - > - - - + ) } diff --git a/app/frontend/Features/Controls/ControlContainer.tsx b/app/frontend/Features/Controls/ControlContainer.tsx new file mode 100644 index 0000000..f9db306 --- /dev/null +++ b/app/frontend/Features/Controls/ControlContainer.tsx @@ -0,0 +1,19 @@ +import { Box } from "@/Components" + +import cx from "clsx" +import * as classes from "./Controls.css" + +interface ControlContainerProps { + children: React.ReactNode + className?: string +} + +const ControlContainer = ({ children, className }: ControlContainerProps) => { + return ( + + { children } + + ) +} + +export default ControlContainer diff --git a/app/frontend/Features/Controls/Controls.css.ts b/app/frontend/Features/Controls/Controls.css.ts index 491357b..c05a197 100644 --- a/app/frontend/Features/Controls/Controls.css.ts +++ b/app/frontend/Features/Controls/Controls.css.ts @@ -3,45 +3,24 @@ import { css } from "@linaria/core" const highlightBorderPx = 4 -export const controlWrapper = css` +export const controlContainer = css` display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: ${vars.spacing.md}; width: 100%; -` - -export const lastButtonClicked = css` - border: ${highlightBorderPx}px solid ${vars.colors.green[4]}; - margin: calc(${vars.spacing.xs} - ${highlightBorderPx}px); -` -export const spacer = css` - border: 2px dotted ${vars.colors.gray[4]}; - border-radius: ${vars.radius.sm}; - min-height: 40px; - margin: ${vars.spacing.xs} 0; -` + padding: ${vars.spacing.md}; -export const button = css` - width: 100%; -` - -export const editButtonIcon = css` - position: absolute; - top: 5px; - right: 13px; - cursor: pointer; - z-index: 100; -` + .control { + &.spacer {} -export const editControl = css` - cursor: auto; + &.slider {} - &:active { - transform: none; + &.button { + width: 100%; + } } ` -export const editControlWrapper = css` - display: inline-block; - position: relative; +export const lastButtonClicked = css` + border: ${highlightBorderPx}px solid ${vars.colors.green[4]}; ` diff --git a/app/frontend/Features/Controls/index.ts b/app/frontend/Features/Controls/index.ts index c58f065..439b32c 100644 --- a/app/frontend/Features/Controls/index.ts +++ b/app/frontend/Features/Controls/index.ts @@ -1,3 +1,2 @@ -export { default as AddControlsInterface } from "./_AddControlsInterface" -export { default as Control, type ControlProps } from "./Control" -export { default as ControlForm } from "./ControlForm" +export { default as Control } from "./Control" +export { default as ControlContainer } from "./ControlContainer" diff --git a/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx index ed6ce7f..f751318 100644 --- a/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/DndEditControlsInterface.tsx @@ -10,13 +10,14 @@ import { import { arrayMove, SortableContext } from "@dnd-kit/sortable" import { useDynamicInputs, useForm } from "use-inertia-form" import { Control } from "@/Features/Controls" +import EditControlWrapper from "./EditControlWrapper" interface DndEditControlsInterfaceProps { screen: Schema.ScreensEdit } const DndEditControlsInterface = ({ screen }: DndEditControlsInterfaceProps) => { - const { addInput, removeInput, paths } = useDynamicInputs({ + const { paths } = useDynamicInputs({ model: "controls", emptyData: { title: "", @@ -71,11 +72,15 @@ const DndEditControlsInterface = ({ screen }: DndEditControlsInterfaceProps) => const record = getData(`${formModel}.${path}`) as Schema.ControlsFormData return ( - + > + + ) }) } diff --git a/app/frontend/Features/Controls/Control/EditControlWrapper.tsx b/app/frontend/Pages/Screens/Edit/Form/EditControlWrapper.tsx similarity index 82% rename from app/frontend/Features/Controls/Control/EditControlWrapper.tsx rename to app/frontend/Pages/Screens/Edit/Form/EditControlWrapper.tsx index 3fc9272..094a887 100644 --- a/app/frontend/Features/Controls/Control/EditControlWrapper.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/EditControlWrapper.tsx @@ -1,18 +1,17 @@ import React from "react" import { Routes } from "@/lib" -import { router } from "@inertiajs/react" -import { Control, type ControlProps } from "@/Features/Controls" import { useSortable } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" -import { Box, BoxProps } from "@mantine/core" +import { Box } from "@mantine/core" import { EditIcon } from "@/Components/Icons" import { modals } from "@mantine/modals" -import ControlForm from "../ControlForm" +import ControlForm from "./ScreenControlForm" import cx from "clsx" -import * as classes from "../Controls.css" +import * as classes from "./EditControls.css" -interface EditControlWrapperProps extends Omit { +interface EditControlWrapperProps { + children: React.ReactNode control: Schema.ControlsFormData } diff --git a/app/frontend/Pages/Screens/Edit/Form/EditControls.css.ts b/app/frontend/Pages/Screens/Edit/Form/EditControls.css.ts new file mode 100644 index 0000000..e52e512 --- /dev/null +++ b/app/frontend/Pages/Screens/Edit/Form/EditControls.css.ts @@ -0,0 +1,39 @@ +import { vars } from "@/lib" +import { css } from "@linaria/core" + +export const editControlsForm = css` + .control { + &.spacer { + border: 2px dotted ${vars.colors.gray[4]}; + border-radius: ${vars.radius.sm}; + min-height: 100%; + } + + &.slider {} + + &.button { + width: 100%; + } + } +` + +export const editButtonIcon = css` + position: absolute; + top: 5px; + right: 13px; + cursor: pointer; + z-index: 100; +` + +export const editControl = css` + cursor: auto; + + &:active { + transform: none; + } +` + +export const editControlWrapper = css` + display: inline-block; + position: relative; +` diff --git a/app/frontend/Features/Controls/ControlForm.tsx b/app/frontend/Pages/Screens/Edit/Form/ScreenControlForm.tsx similarity index 83% rename from app/frontend/Features/Controls/ControlForm.tsx rename to app/frontend/Pages/Screens/Edit/Form/ScreenControlForm.tsx index e59f6fb..c5f714a 100644 --- a/app/frontend/Features/Controls/ControlForm.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/ScreenControlForm.tsx @@ -5,15 +5,15 @@ import { ProtocolDropdown } from "@/Components/Dropdowns" import { useGetProtocol } from "@/queries" import { FormProps } from "use-inertia-form" -type EditControlFormData = { - control: Schema.ControlsFormData +type ScreenControlFormData = { + control: Partial } -export interface EditControlFormProps extends Omit, "data"> { +export interface EditScreenControlFormProps extends Omit, "data"> { control?: Schema.ControlsFormData } -const EditControlForm = ({ control, ...props }: EditControlFormProps) => { +const ScreenControlForm = ({ control, ...props }: EditScreenControlFormProps) => { const [showingProtocolSlug, setShowingProtocolSlug] = useState(control?.protocol?.slug || "") const { data } = useGetProtocol({ slug: showingProtocolSlug }, { @@ -22,7 +22,7 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { }) return ( - + model="control" data={ control ? { control } : undefined } remember={ false } @@ -37,7 +37,7 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { } - >{ ({ data }) => <> + >{ ({ data }) => <> { data.control.control_type !== "spacer" && { @@ -65,7 +65,7 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { } - >{ ({ data }) => <> + >{ ({ data }) => <> { data.control.control_type !== "spacer" && @@ -87,4 +87,4 @@ const EditControlForm = ({ control, ...props }: EditControlFormProps) => { ) } -export default EditControlForm +export default ScreenControlForm diff --git a/app/frontend/Pages/Screens/Edit/Form/index.tsx b/app/frontend/Pages/Screens/Edit/Form/index.tsx index d12084e..39b1539 100644 --- a/app/frontend/Pages/Screens/Edit/Form/index.tsx +++ b/app/frontend/Pages/Screens/Edit/Form/index.tsx @@ -1,8 +1,12 @@ import React from "react" import { Routes } from "@/lib" -import { Divider, Flex } from "@/Components" +import { Divider } from "@/Components" import { Form, Submit } from "@/Components/Form" import DndEditControlsInterface from "./DndEditControlsInterface" +import { ControlContainer } from "@/Features/Controls" + +import cx from "clsx" +import * as classes from "./EditControls.css" interface EditScreenFormProps { screen: Schema.ScreensEdit @@ -19,9 +23,9 @@ const EditScreenForm = ({ screen }: EditScreenFormProps) => { remember={ false } > - + - + diff --git a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx index f533ef5..d9e9bfe 100644 --- a/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx +++ b/app/frontend/Pages/Screens/Edit/NewControlMenu/index.tsx @@ -3,7 +3,7 @@ import { Affix, Button, Menu } from "@/Components" import { modals } from "@mantine/modals" import { Routes } from "@/lib" import { useCreateControl } from "@/queries" -import { ControlForm } from "@/Features/Controls" +import ControlForm from "../Form/ScreenControlForm" const controlFormFilter = ["control.id", "control.command", "control.updated_at", "control.created_at", "control.command_id", "control.protocol"] diff --git a/app/frontend/Pages/Screens/Show/index.tsx b/app/frontend/Pages/Screens/Show/index.tsx index 68a00da..a4b721e 100644 --- a/app/frontend/Pages/Screens/Show/index.tsx +++ b/app/frontend/Pages/Screens/Show/index.tsx @@ -1,9 +1,10 @@ import React from "react" -import { Flex, Page, Tabs } from "@/Components" +import { Page, Tabs } from "@/Components" import Control from "../../../Features/Controls/Control" import { Routes } from "@/lib" import { useLocation } from "@/lib/hooks" import { router } from "@inertiajs/react" +import { ControlContainer } from "@/Features/Controls" interface IShowScreenProps { screen: Schema.ScreensShow @@ -32,11 +33,11 @@ const ShowScreen = ({ screen, screens }: IShowScreenProps) => { { screens.map(iScreen => ( { iScreen.id === screen.id && - + { screen?.controls?.map(control => ( )) } - + } )) } diff --git a/app/frontend/types/serializers/Control.d.ts b/app/frontend/types/serializers/Control.d.ts index a171d3d..7d88e27 100644 --- a/app/frontend/types/serializers/Control.d.ts +++ b/app/frontend/types/serializers/Control.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 11440afa5ce58484a6ab36473b4d78fb +// TypesFromSerializers CacheKey 0bc106863143a01426135d7263f91a66 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Command from './Command' @@ -11,7 +11,7 @@ declare global { color?: string command: Command command_id?: number - control_type: string + control_type: "button" | "slider" | "spacer" max_value?: string | number min_value?: string | number order: number diff --git a/app/frontend/types/serializers/Controls/Edit.d.ts b/app/frontend/types/serializers/Controls/Edit.d.ts index 7ccd160..1d62002 100644 --- a/app/frontend/types/serializers/Controls/Edit.d.ts +++ b/app/frontend/types/serializers/Controls/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 0770a783eb2bc87c468393c865015831 +// TypesFromSerializers CacheKey 49b1efb72ac927272bfc40e401c3b239 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Command from '../Command' @@ -11,7 +11,7 @@ declare global { color?: string command: Command command_id?: number - control_type: string + control_type: "button" | "slider" | "spacer" created_at: string | Date max_value?: string | number min_value?: string | number diff --git a/app/frontend/types/serializers/Controls/FormData.d.ts b/app/frontend/types/serializers/Controls/FormData.d.ts index f15b686..d1fa7fb 100644 --- a/app/frontend/types/serializers/Controls/FormData.d.ts +++ b/app/frontend/types/serializers/Controls/FormData.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey c8de8d04021d5bb40752cd5964e3c702 +// TypesFromSerializers CacheKey 37cff8a2614851e20ca11699c31bc654 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type CommandsFormData from '../Commands/FormData' @@ -11,7 +11,7 @@ declare global { color?: string command?: CommandsFormData command_id?: number - control_type: string + control_type: "button" | "slider" | "spacer" max_value?: string | number min_value?: string | number order?: number diff --git a/app/frontend/types/serializers/Controls/Index.d.ts b/app/frontend/types/serializers/Controls/Index.d.ts index fbea870..4cd6310 100644 --- a/app/frontend/types/serializers/Controls/Index.d.ts +++ b/app/frontend/types/serializers/Controls/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 7a569e8cd6a73a61659a0b1a7e0af640 +// TypesFromSerializers CacheKey 581b9d49d2db3413d036e0ad578ea5c8 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Command from '../Command' @@ -11,7 +11,7 @@ declare global { color?: string command: Command command_id?: number - control_type: string + control_type: "button" | "slider" | "spacer" created_at: string | Date max_value?: string | number min_value?: string | number diff --git a/app/frontend/types/serializers/Controls/Show.d.ts b/app/frontend/types/serializers/Controls/Show.d.ts index 41ab671..ee8f93b 100644 --- a/app/frontend/types/serializers/Controls/Show.d.ts +++ b/app/frontend/types/serializers/Controls/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 782a49f9116e49518e97dc384581e995 +// TypesFromSerializers CacheKey e81f3516f4adac05308c1a02d39ffa98 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. import type Command from '../Command' @@ -11,7 +11,7 @@ declare global { color?: string command?: Command command_id?: number - control_type: string + control_type: "button" | "slider" | "spacer" created_at: string | Date max_value?: string | number min_value?: string | number diff --git a/app/serializers/control_serializer.rb b/app/serializers/control_serializer.rb index fd090fd..e083aab 100644 --- a/app/serializers/control_serializer.rb +++ b/app/serializers/control_serializer.rb @@ -41,7 +41,7 @@ class ControlSerializer < ApplicationSerializer :screen_id, :protocol_id, :command_id, - control_type: { type: :string }, + control_type: { type: Control.control_types.keys.map { |k| "\"#{k}\"" }.join(" | ") }, ) belongs_to :protocol, serializer: ProtocolSerializer From fc66d03b28100bff249a76cd17ed40ab2ff55aa5 Mon Sep 17 00:00:00 2001 From: Avram Walden Date: Sun, 2 Feb 2025 16:59:38 -0800 Subject: [PATCH 15/15] test: adds all the tests --- .rubocop.yml | 65 +++++---- app/controllers/api/controls_controller.rb | 20 +-- app/controllers/api/protocols_controller.rb | 5 +- app/controllers/commands_controller.rb | 1 + app/controllers/controls_controller.rb | 10 +- app/controllers/pages_controller.rb | 15 -- app/controllers/protocols_controller.rb | 1 + .../users/confirmations_controller.rb | 6 +- app/controllers/users/passwords_controller.rb | 2 +- .../users/registrations_controller.rb | 4 +- app/controllers/users/sessions_controller.rb | 2 +- .../Controls/Control/components/Button.tsx | 1 + .../Controls/Control/components/Slider.tsx | 2 +- .../Controls/Control/components/Spacer.tsx | 2 +- app/frontend/Pages/Commands/Table.tsx | 8 +- app/frontend/Pages/Pages/Home/index.tsx | 13 -- app/frontend/Pages/Protocols/Table.tsx | 8 +- .../Pages/Settings/Appearance/Index/index.tsx | 4 +- app/frontend/types/serializers/Server.d.ts | 4 +- .../types/serializers/Servers/Edit.d.ts | 4 +- .../types/serializers/Servers/FormData.d.ts | 4 +- .../types/serializers/Servers/Index.d.ts | 4 +- .../types/serializers/Servers/Reference.d.ts | 4 +- .../types/serializers/Servers/Show.d.ts | 4 +- app/jobs/osc_job.rb | 21 ++- app/jobs/send_osc_commands_job.rb | 4 +- app/jobs/send_osc_protocol_job.rb | 1 - app/models/command.rb | 6 +- app/models/command_value.rb | 2 + app/models/control.rb | 6 +- app/models/protocol.rb | 4 +- app/models/screen.rb | 14 +- app/models/server.rb | 8 +- app/models/user.rb | 13 +- app/policies/control_policy.rb | 4 + app/serializers/server_serializer.rb | 2 +- config/environments/test.rb | 2 +- db/migrate/20240229063401_create_servers.rb | 2 +- db/schema.rb | 2 +- db/seeds.rb | 1 - db/seeds/01_required.rb | 8 +- public/404.html | 2 +- public/422.html | 2 +- public/500.html | 2 +- spec/factories/commands.rb | 25 +++- spec/factories/controls.rb | 20 +-- spec/factories/protocols.rb | 2 +- spec/factories/protocols_commands.rb | 2 +- spec/factories/roles.rb | 21 --- spec/factories/screens.rb | 4 +- spec/factories/servers.rb | 2 +- spec/factories/users.rb | 5 +- spec/jobs/send_osc_command_job_spec.rb | 25 ---- spec/jobs/send_osc_commands_job_spec.rb | 26 ++++ spec/jobs/send_osc_protocol_job_spec.rb | 66 ++++++++- spec/models/command_spec.rb | 66 ++++++++- spec/models/command_value_spec.rb | 50 ++++++- spec/models/control_spec.rb | 93 +++++++++++- spec/models/protocol_spec.rb | 91 +++++++++++- spec/models/protocols_command_spec.rb | 101 ++++++++++++- spec/models/role_spec.rb | 21 --- spec/models/screen_spec.rb | 67 ++++++++- spec/models/server_spec.rb | 22 ++- spec/models/user_spec.rb | 44 +++++- spec/policies/server_policy_spec.rb | 27 ---- spec/policies/user_policy_spec.rb | 27 ---- spec/rails_helper.rb | 16 +-- spec/requests/api/commands_spec.rb | 5 +- spec/requests/api/controls_spec.rb | 63 +++++++- spec/requests/api/protocols_spec.rb | 131 ++++++++++++++++- spec/requests/api/servers_spec.rb | 18 ++- spec/requests/commands_spec.rb | 95 ++++++------- spec/requests/controls_spec.rb | 134 ++++++------------ spec/requests/pages_spec.rb | 7 - spec/requests/protocols_commands_spec.rb | 134 ------------------ spec/requests/protocols_spec.rb | 72 +++------- spec/requests/screens_spec.rb | 98 ++++++------- spec/requests/servers_spec.rb | 78 ++++------ spec/services/osc_service_spec.rb | 8 +- spec/support/request_macros.rb | 17 +-- 80 files changed, 1215 insertions(+), 767 deletions(-) delete mode 100644 app/controllers/pages_controller.rb delete mode 100644 app/frontend/Pages/Pages/Home/index.tsx delete mode 100644 spec/factories/roles.rb delete mode 100644 spec/jobs/send_osc_command_job_spec.rb create mode 100644 spec/jobs/send_osc_commands_job_spec.rb delete mode 100644 spec/models/role_spec.rb delete mode 100644 spec/policies/server_policy_spec.rb delete mode 100644 spec/policies/user_policy_spec.rb delete mode 100644 spec/requests/pages_spec.rb delete mode 100644 spec/requests/protocols_commands_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 671ded0..3979bc4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,7 +4,6 @@ require: - rubocop-rspec AllCops: - Exclude: - "bin/**/*" - "node_modules/**/*" @@ -13,14 +12,13 @@ AllCops: - ".git/**/*" NewCops: enable -Rails/InverseOf: - Enabled: true - +Rails/HasAndBelongsToMany: + Enabled: false Style/Documentation: Enabled: false Style/ExponentialNotation: Enabled: true -Style/HashAsLastArrayItem: +Style/HashAsLastArrayItem: Enabled: false Style/HashEachMethods: Enabled: true @@ -31,7 +29,8 @@ Style/HashTransformValues: Style/FrozenStringLiteralComment: Enabled: false Style/StringLiterals: - Enabled: false + Enabled: true + EnforcedStyle: double_quotes Style/NumericLiterals: Enabled: false Style/ClassAndModuleChildren: @@ -39,8 +38,7 @@ Style/ClassAndModuleChildren: Style/EmptyMethod: Enabled: false Style/GuardClause: - # Enabled: false - Enabled: true + Enabled: false Style/SlicingWithRange: Enabled: true Style/RedundantRegexpCharacterClass: @@ -72,19 +70,13 @@ Lint/MissingSuper: Layout/EmptyLinesAroundClassBody: Enabled: false Layout/FirstHashElementIndentation: - # Enabled: false - Enabled: true - EnforcedStyle: consistent -Layout/EmptyLinesAroundAttributeAccessor: - # Enabled: true Enabled: false - AllowAliasSyntax: true +Layout/EmptyLinesAroundAttributeAccessor: + Enabled: true Layout/MultilineBlockLayout: Enabled: false - # Enabled: true Layout/BlockEndNewline: Enabled: false - # Enabled: true Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented @@ -95,19 +87,19 @@ Layout/EmptyLinesAroundBlockBody: Layout/LineLength: Enabled: false Layout/ExtraSpacing: - # Enabled: false - Enabled: true - AllowForAlignment: true + Enabled: false Layout/TrailingWhitespace: Enabled: true Layout/SpaceAroundMethodCallOperator: Enabled: true Layout/SpaceInsideHashLiteralBraces: Enabled: true +Layout/SpaceInsideBlockBraces: + Enabled: true Layout/CommentIndentation: Enabled: false Layout/HashAlignment: - Enabled: true + Enabled: false Lint/DeprecatedOpenSSLConstant: Enabled: true Lint/MixedRegexpCaptureTypes: @@ -246,7 +238,7 @@ Layout/DotPosition: StyleGuide: https://relaxed.ruby.style/#layoutdotposition Layout/SpaceBeforeBlockBraces: - EnforcedStyle: space + Enabled: false StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces Layout/SpaceInsideParens: @@ -254,13 +246,15 @@ Layout/SpaceInsideParens: StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens Layout/FirstArrayElementIndentation: - EnforcedStyle: consistent + Enabled: false Layout/MultilineOperationIndentation: - EnforcedStyle: "indented" + Enabled: true + EnforcedStyle: indented -Layout/ArgumentAlignment: - EnforcedStyle: with_fixed_indentation +Layout/AssignmentIndentation: + Enabled: true + IndentationWidth: 2 Lint/AmbiguousRegexpLiteral: Enabled: false @@ -270,6 +264,21 @@ Lint/AssignmentInCondition: Enabled: false StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition +Layout/ArgumentAlignment: + Enabled: false + +Layout/IndentationWidth: + Width: 2 + +Layout/IndentationConsistency: + EnforcedStyle: normal + +Layout/CaseIndentation: + Enabled: true + EnforcedStyle: end + IndentOneStep: false + IndentationWidth: 2 + Naming/AccessorMethodName: Enabled: false @@ -281,3 +290,9 @@ Style/HashSyntax: Metrics: Enabled: false + +RSpec/NestedGroups: + Enabled: false + +Lint/DuplicateBranch: + Enabled: false diff --git a/app/controllers/api/controls_controller.rb b/app/controllers/api/controls_controller.rb index bc4c6b8..d861ea0 100644 --- a/app/controllers/api/controls_controller.rb +++ b/app/controllers/api/controls_controller.rb @@ -1,8 +1,8 @@ class Api::ControlsController < ApplicationController - expose :control expose :controls, -> { Control.all } + expose :control - strong_params :control, permit: [:title, :control_type, :order, :color, :screen_id, :min_value, :max_value, :value, :protocol_id] + strong_params :control, permit: [:title, :control_type, :order, :color, :screen_id, :min_value, :max_value, :value, :command_id, :protocol_id] # @route GET /api/options/controls (api_controls_options) def options @@ -10,18 +10,10 @@ def options render json: controls.render(view: :options), status: :ok end - def execute - authorize control - - control.create_activity key: :slug, owner: current_user - - SendOscProtocolJob.perform_later(control) - - render json: control, status: :accepted - end - # @route POST /api/controls (api_controls) def create + authorize Control.new + if control.save render json: control.render, status: :created else @@ -32,7 +24,9 @@ def create # @route PATCH /api/controls/:id (api_control) # @route PUT /api/controls/:id (api_control) def update - if control.save + authorize control + + if control.update(control_params) render json: control.render, status: :created else render json: { errors: control.errors }, status: :not_acceptable diff --git a/app/controllers/api/protocols_controller.rb b/app/controllers/api/protocols_controller.rb index f83c77e..f722f4e 100644 --- a/app/controllers/api/protocols_controller.rb +++ b/app/controllers/api/protocols_controller.rb @@ -24,10 +24,9 @@ def options def execute authorize protocol - protocol.create_activity key: :slug, owner: current_user + protocol.create_activity :execute, owner: current_user SendOscProtocolJob.perform_later(protocol) - render json: protocol, status: :accepted end @@ -43,7 +42,7 @@ def create # @route PATCH /api/protocols/:slug (api_protocol) # @route PUT /api/protocols/:slug (api_protocol) def update - if protocol.save + if protocol.update(protocol_params) render json: protocol.render, status: :created else render json: { errors: protocol.errors }, status: :not_acceptable diff --git a/app/controllers/commands_controller.rb b/app/controllers/commands_controller.rb index 1dbfb89..52f3fb0 100644 --- a/app/controllers/commands_controller.rb +++ b/app/controllers/commands_controller.rb @@ -48,6 +48,7 @@ def edit # @route POST /commands (commands) def create authorize Command.new + if command.save redirect_to commands_path, notice: "Command was successfully created." else diff --git a/app/controllers/controls_controller.rb b/app/controllers/controls_controller.rb index f4b8e49..7d3dc56 100644 --- a/app/controllers/controls_controller.rb +++ b/app/controllers/controls_controller.rb @@ -1,10 +1,10 @@ class ControlsController < ApplicationController expose :controls, -> { search(Control.includes_associated, sortable_fields) } - expose :control, find: ->(id, scope) { scope.includes_associated.find(id) } + expose :control, scope: -> { Control.includes_associated } sortable_fields %w(title type screen_id min_value max_value value protocol_id) - strong_params :control, permit: [:title, :control_type, :order, :color, :screen_id, :min_value, :max_value, :value, :protocol_id] + strong_params :control, permit: [:title, :control_type, :order, :color, :screen_id, :min_value, :max_value, :value, :command_id, :protocol_id] # @route POST /controls (controls) def create @@ -13,7 +13,7 @@ def create if control.save redirect_to edit_screen_path(control.screen), notice: "Control was successfully created." else - redirect_to new_control_path, inertia: { errors: control.errors } + redirect_to edit_screen_path(control.screen), inertia: { errors: control.errors } end end @@ -25,7 +25,7 @@ def update if control.update(control_params) redirect_to edit_screen_path(control.screen), inertia: { method: :get }, notice: "Control was successfully updated." else - redirect_to edit_control_path, inertia: { errors: control.errors } + redirect_to edit_screen_path(control.screen), inertia: { errors: control.errors } end end @@ -33,6 +33,6 @@ def update def destroy authorize control control.destroy! - redirect_to controls_url, notice: "Control was successfully destroyed." + redirect_to edit_screen_path(control.screen), notice: "Control was successfully destroyed." end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb deleted file mode 100644 index 825d7ee..0000000 --- a/app/controllers/pages_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -class PagesController < ApplicationController - skip_before_action :authenticate_user!, only: [:home] - - # GET / - # @route GET / (root) - def home - render inertia: "Public/Pages/Home", props: {} - end - - def dev - render inertia: "Pages/Dev", props: { - protocol: Protocol.first.render(view: :show) - } - end -end diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index f553e1d..b1285dc 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -52,6 +52,7 @@ def edit # @route POST /protocols (protocols) def create authorize Protocol.new + if protocol.save redirect_to protocol, notice: "Protocol was successfully created." else diff --git a/app/controllers/users/confirmations_controller.rb b/app/controllers/users/confirmations_controller.rb index f9c3561..22891c4 100644 --- a/app/controllers/users/confirmations_controller.rb +++ b/app/controllers/users/confirmations_controller.rb @@ -24,7 +24,7 @@ def show # @route GET /users/confirmation/new (new_user_confirmation) def new - render inertia: "Auth/Devise/Confirmations/New", props: { + render inertia: "Devise/Confirmations/New", props: { user: { email: params[:email] } @@ -47,7 +47,7 @@ def create # The path used after resending confirmation instructions. def after_resending_confirmation_instructions_path_for(resource_name) - is_navigational_format? ? new_session_path(resource_name) : '/' + is_navigational_format? ? new_session_path(resource_name) : "/" end # The path used after confirmation. @@ -60,6 +60,6 @@ def after_confirmation_path_for(resource_name, resource) end def translation_scope - 'devise.confirmations' + "devise.confirmations" end end diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb index 1ec7846..227da42 100644 --- a/app/controllers/users/passwords_controller.rb +++ b/app/controllers/users/passwords_controller.rb @@ -3,7 +3,7 @@ class Users::PasswordsController < Devise::PasswordsController # @route GET /users/password/new (new_user_password) def new - render inertia: "Auth/Devise/Passwords/New" + render inertia: "Devise/Passwords/New" end # def create diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index ca9fa43..4b0e19d 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -10,11 +10,11 @@ def new first_run = false if User.count == 0 flash.clear - flash[:notice] = t('devise.registrations.first_run_create_admin') + flash[:notice] = t("devise.registrations.first_run_create_admin") first_run = true end - render inertia: 'Auth/Devise/Register', props: { + render inertia: "Devise/Register", props: { user: User.new, first_run:, } diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index aa59cb4..40327da 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -6,7 +6,7 @@ class Users::SessionsController < Devise::SessionsController # @route GET /login (new_user_session) def new - render inertia: "Auth/Devise/Login" + render inertia: "Devise/Login" end # @route POST /login (user_session) diff --git a/app/frontend/Features/Controls/Control/components/Button.tsx b/app/frontend/Features/Controls/Control/components/Button.tsx index 4986138..8a3160a 100644 --- a/app/frontend/Features/Controls/Control/components/Button.tsx +++ b/app/frontend/Features/Controls/Control/components/Button.tsx @@ -22,6 +22,7 @@ const ControlButton = ({ const handleButtonClick = (e: React.MouseEvent) => { e.preventDefault() + // Do nothing if disabled or the control has not been persisted if(disable || !control?.id) return const route = controlRoute(control) diff --git a/app/frontend/Features/Controls/Control/components/Slider.tsx b/app/frontend/Features/Controls/Control/components/Slider.tsx index 7ee70c8..1bb90e7 100644 --- a/app/frontend/Features/Controls/Control/components/Slider.tsx +++ b/app/frontend/Features/Controls/Control/components/Slider.tsx @@ -6,7 +6,7 @@ import cx from "clsx" export type ControlSpacerBaseProps = ControlProps & {} -const ControlSpacerBase = ({ control, className, ...props }: ControlSpacerBaseProps) => { +const ControlSpacerBase = ({ control, disable, className, ...props }: ControlSpacerBaseProps) => { return ( { +const ControlSpacerBase = ({ control, disable, className, ...props }: ControlProps) => { return ( { @@ -32,8 +32,10 @@ const CommandTable = (props: TableProps) => { - - + + + + diff --git a/app/frontend/Pages/Pages/Home/index.tsx b/app/frontend/Pages/Pages/Home/index.tsx deleted file mode 100644 index a59743f..0000000 --- a/app/frontend/Pages/Pages/Home/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import { Box, Title, Text } from "@/Components" - -const Home = () => { - return ( - - OSC - This is a placeholder page for the public section of the application. - - ) -} - -export default Home diff --git a/app/frontend/Pages/Protocols/Table.tsx b/app/frontend/Pages/Protocols/Table.tsx index 99a1401..382f6d8 100644 --- a/app/frontend/Pages/Protocols/Table.tsx +++ b/app/frontend/Pages/Protocols/Table.tsx @@ -1,6 +1,6 @@ import React from "react" import { Routes } from "@/lib" -import { Table, Link, type TableProps } from "@/Components" +import { Table, Link, type TableProps, Group } from "@/Components" import { DeleteButton, EditButton } from "@/Components/Button" const ProtocolTable = (props: TableProps) => { @@ -24,8 +24,10 @@ const ProtocolTable = (props: TableProps) => { { protocol.commands.length } - - + + + + diff --git a/app/frontend/Pages/Settings/Appearance/Index/index.tsx b/app/frontend/Pages/Settings/Appearance/Index/index.tsx index b395949..44c1e28 100644 --- a/app/frontend/Pages/Settings/Appearance/Index/index.tsx +++ b/app/frontend/Pages/Settings/Appearance/Index/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef } from "react" import { Box, Title } from "@/Components" import SettingsLayout from "../../SettingsLayout" -import useLayoutStore from "@/lib/store/LayoutStore" +import useStore from "@/lib/store" interface IAppearanceSettingsProps { settings: { @@ -10,7 +10,7 @@ interface IAppearanceSettingsProps { } const AppearanceSettings = ({ settings }: IAppearanceSettingsProps) => { - const { primaryColor, setPrimaryColor } = useLayoutStore() + const { primaryColor, setPrimaryColor } = useStore() const RevertColorRef = useRef(primaryColor!) useEffect(() => { diff --git a/app/frontend/types/serializers/Server.d.ts b/app/frontend/types/serializers/Server.d.ts index 7e8829a..ea8d41a 100644 --- a/app/frontend/types/serializers/Server.d.ts +++ b/app/frontend/types/serializers/Server.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 1d0ebd771dfaa1616c2b8cc2815f3579 +// TypesFromSerializers CacheKey 40aabc61d8d8aa6ed654b56786781183 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -9,7 +9,7 @@ declare global { id?: number slug?: string description?: string - hostname?: string + hostname: string port?: number title: string } diff --git a/app/frontend/types/serializers/Servers/Edit.d.ts b/app/frontend/types/serializers/Servers/Edit.d.ts index 98e3564..7996a54 100644 --- a/app/frontend/types/serializers/Servers/Edit.d.ts +++ b/app/frontend/types/serializers/Servers/Edit.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey f339f7c6ee20beabffa34c27dd8b94d5 +// TypesFromSerializers CacheKey 86721184a9f27404319bdc672e4b837a // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -9,7 +9,7 @@ declare global { id: number created_at: string | Date description?: string - hostname?: string + hostname: string port?: number slug: string title: string diff --git a/app/frontend/types/serializers/Servers/FormData.d.ts b/app/frontend/types/serializers/Servers/FormData.d.ts index 7c9c49a..9960bf5 100644 --- a/app/frontend/types/serializers/Servers/FormData.d.ts +++ b/app/frontend/types/serializers/Servers/FormData.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 84d128d8fe827615484a2b15377c8951 +// TypesFromSerializers CacheKey 5dd6b66905e60008cfff90d5da69b3f9 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -9,7 +9,7 @@ declare global { id?: number slug?: string description?: string - hostname?: string + hostname: string port?: number title: string } diff --git a/app/frontend/types/serializers/Servers/Index.d.ts b/app/frontend/types/serializers/Servers/Index.d.ts index 848e806..e9ac508 100644 --- a/app/frontend/types/serializers/Servers/Index.d.ts +++ b/app/frontend/types/serializers/Servers/Index.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 716bc833e9c693f93d1559387bf54345 +// TypesFromSerializers CacheKey f4d136286c2e492204840de9b4897383 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -9,7 +9,7 @@ declare global { id: number created_at: string | Date description?: string - hostname?: string + hostname: string port?: number slug: string title: string diff --git a/app/frontend/types/serializers/Servers/Reference.d.ts b/app/frontend/types/serializers/Servers/Reference.d.ts index 62c5e15..38828b6 100644 --- a/app/frontend/types/serializers/Servers/Reference.d.ts +++ b/app/frontend/types/serializers/Servers/Reference.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 94c79fec6d06b1a04c3b467c3f749357 +// TypesFromSerializers CacheKey 1849ca7af9fd288fed83589abde6bcbd // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -9,7 +9,7 @@ declare global { id: number created_at: string | Date description?: string - hostname?: string + hostname: string port?: number slug: string title: string diff --git a/app/frontend/types/serializers/Servers/Show.d.ts b/app/frontend/types/serializers/Servers/Show.d.ts index d0a73a6..2e52395 100644 --- a/app/frontend/types/serializers/Servers/Show.d.ts +++ b/app/frontend/types/serializers/Servers/Show.d.ts @@ -1,4 +1,4 @@ -// TypesFromSerializers CacheKey 1265f32fcf64780e057802ccf4bb49ae +// TypesFromSerializers CacheKey e2851dc9ea3d1728e6dda994f8de6031 // // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers. export {} @@ -9,7 +9,7 @@ declare global { id: number created_at: string | Date description?: string - hostname?: string + hostname: string port?: number slug: string title: string diff --git a/app/jobs/osc_job.rb b/app/jobs/osc_job.rb index 50bce85..b8784b3 100644 --- a/app/jobs/osc_job.rb +++ b/app/jobs/osc_job.rb @@ -3,22 +3,33 @@ class OscJob < ApplicationJob private def send_osc_command(command, client = nil) - client ||= udp_client_from_server(command[:server]) + command_hash = command_hash(command) + + client ||= udp_client_from_server(command_hash[:server]) - # pry command message = OscService::Message.new( - command[:address], - cast_to_type(command[:value], command[:payload_type]), + command_hash[:address], + cast_to_type(command_hash[:value], command_hash[:payload_type]), ) client.send(message) rescue Errno::ECONNREFUSED => e - server = command[:server] || Server.find(command[:server_id]) + server = command_hash[:server] || Server.find(command_hash[:server_id]) log_connection_error(server, e) rescue StandardError => e Rails.logger.error({ error: e }) end + def command_hash(command) + if command.respond_to?(:attributes) + command.attributes.with_indifferent_access.merge( + server: command.server.attributes.with_indifferent_access, + ) + else + command + end + end + def udp_client_from_server(server) OscService::Client.new( host: server[:hostname], diff --git a/app/jobs/send_osc_commands_job.rb b/app/jobs/send_osc_commands_job.rb index a696b1c..5726dbd 100644 --- a/app/jobs/send_osc_commands_job.rb +++ b/app/jobs/send_osc_commands_job.rb @@ -7,12 +7,12 @@ def perform(commands, server = nil) client = udp_client_from_server(server) end - if commands.respond_to?('each') + if commands.respond_to?("each") commands.each do |command| send_osc_command(command, client) end else - send_osc_command(command, client) + send_osc_command(commands, client) end end end diff --git a/app/jobs/send_osc_protocol_job.rb b/app/jobs/send_osc_protocol_job.rb index f610e57..bd6f28d 100644 --- a/app/jobs/send_osc_protocol_job.rb +++ b/app/jobs/send_osc_protocol_job.rb @@ -18,7 +18,6 @@ def perform(protocol) commands_grouped_by_delay.push(command) next end - # When a command has a delay, send off the batch and start fresh delay += command[:delay] diff --git a/app/models/command.rb b/app/models/command.rb index f2ae6bb..e3f19a1 100644 --- a/app/models/command.rb +++ b/app/models/command.rb @@ -29,7 +29,7 @@ class Command < ApplicationRecord pg_search_scope( :search, against: [:title, :address, :payload, :description], - assoicated_against: { + associated_against: { protocols: [:title, :description] }, using: { @@ -42,6 +42,8 @@ class Command < ApplicationRecord enum :payload_type, { integer: 0, float: 1, string: 2, blob: 3, time: 4, symbol: 5, character: 6, boolean: 7 } + attribute :allow_custom_value, :boolean, default: false + slug :title has_many :protocols_commands, dependent: :destroy @@ -50,6 +52,8 @@ class Command < ApplicationRecord belongs_to :server + validates :title, presence: true + scope :includes_associated, -> { includes([:protocols_commands, :protocols, :command_values]) } accepts_nested_attributes_for :command_values, allow_destroy: true diff --git a/app/models/command_value.rb b/app/models/command_value.rb index 2f3b8b7..10e4319 100644 --- a/app/models/command_value.rb +++ b/app/models/command_value.rb @@ -20,5 +20,7 @@ class CommandValue < ApplicationRecord resourcify + validates :value, presence: true + belongs_to :command end diff --git a/app/models/control.rb b/app/models/control.rb index c1be49e..943bc1d 100644 --- a/app/models/control.rb +++ b/app/models/control.rb @@ -36,7 +36,7 @@ class Control < ApplicationRecord pg_search_scope( :search, - against: [:title, :type, :screen, :order, :min_value, :max_value, :value, :protocol], + against: [:title, :control_type, :screen, :order, :min_value, :max_value, :value, :protocol], associated_against: { screen: [], protocol: [], }, @@ -58,6 +58,8 @@ class Control < ApplicationRecord validate :protocol_xor_command, if: -> { self.control_type != "spacer" } validates :order, presence: true + validates :title, presence: true + validates :control_type, presence: true private @@ -79,6 +81,6 @@ def set_spacer_title def protocol_xor_command return if protocol.blank? ^ command.blank? - errors.add(:protocol_xor_command, 'A control must reference either a protocol or a command') + errors.add(:protocol_xor_command, "A control must reference either a protocol or a command") end end diff --git a/app/models/protocol.rb b/app/models/protocol.rb index 2f6bff9..9ed1e82 100644 --- a/app/models/protocol.rb +++ b/app/models/protocol.rb @@ -41,10 +41,12 @@ class Protocol < ApplicationRecord protocols_commands.*, COALESCE(protocols_commands.value, command_values.value) AS value ') - .joins('LEFT JOIN command_values ON command_values.id = protocols_commands.command_value_id') + .joins("LEFT JOIN command_values ON command_values.id = protocols_commands.command_value_id") .includes([:server, :command_values]) }, through: :protocols_commands, dependent: :nullify + validates :title, presence: true + scope :includes_associated, -> { includes([:protocols_commands]) } accepts_nested_attributes_for :protocols_commands, allow_destroy: true diff --git a/app/models/screen.rb b/app/models/screen.rb index d6db9c2..d489dd3 100644 --- a/app/models/screen.rb +++ b/app/models/screen.rb @@ -32,14 +32,12 @@ class Screen < ApplicationRecord slug :title - has_many :controls, -> { order(order: :asc) }, dependent: :nullify, inverse_of: :screen + has_many :controls, -> { order(order: :asc) }, dependent: :destroy, inverse_of: :screen default_scope { order(:order) } scope :includes_associated, -> { includes([:controls]) } - validates :title, format: { without: /new/ } - - accepts_nested_attributes_for :controls, reject_if: ->(attributes) { attributes['title'].blank? }, allow_destroy: true + accepts_nested_attributes_for :controls, reject_if: ->(attributes) { attributes["title"].blank? }, allow_destroy: true private @@ -47,6 +45,12 @@ def set_screen_order return unless self.order.nil? last_screen = Screen.order(:order).last - self.order = last_screen.nil? ? 1 : last_screen.order + 1 + + self.order = if last_screen.nil? || last_screen.order.nil? + 1 + else + last_screen.order + 1 + end end + end diff --git a/app/models/server.rb b/app/models/server.rb index 2b14032..4a40ffd 100644 --- a/app/models/server.rb +++ b/app/models/server.rb @@ -4,7 +4,7 @@ # # id :bigint not null, primary key # description :text -# hostname :string +# hostname :string not null # port :integer # slug :string not null # title :string not null @@ -34,7 +34,9 @@ class Server < ApplicationRecord has_many :commands, dependent: :nullify - scope :includes_associated, -> { includes([:commands]) } + attribute :port, :integer, default: 9091 + + validates :hostname, presence: true - attribute :port, :integer, default: 8080 + scope :includes_associated, -> { includes([:commands]) } end diff --git a/app/models/user.rb b/app/models/user.rb index e6d21d3..9d7755d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -55,16 +55,15 @@ class User < ApplicationRecord # :omniauthable, :timeoutable, :confirmable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :lockable, :trackable - scope :includes_associated, -> { includes([:circles]) } + password_complexity_regex = /\A(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-.]).{8,70}\z/ + validates :password, format: { with: password_complexity_regex }, on: [:create, :update], confirmation: true, if: :password - belongs_to :person, optional: true - - def circles - Circle.with_roles(Circle.find_roles.pluck(:name), self) - end + validates :email, presence: true, uniqueness: true + validates :email, length: { maximum: 255 } + validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } # Rows page for pagination def limit(model) - self.table_preferences&.[](model.to_s)&.[]('limit') + self.table_preferences&.[](model.to_s)&.[]("limit") end end diff --git a/app/policies/control_policy.rb b/app/policies/control_policy.rb index 6d69f6b..b7d1250 100644 --- a/app/policies/control_policy.rb +++ b/app/policies/control_policy.rb @@ -1,4 +1,8 @@ class ControlPolicy < ApplicationPolicy class Scope < ApplicationPolicy::Scope end + + def options? + standard_auth(:options) + end end diff --git a/app/serializers/server_serializer.rb b/app/serializers/server_serializer.rb index 27af59b..36557c7 100644 --- a/app/serializers/server_serializer.rb +++ b/app/serializers/server_serializer.rb @@ -4,7 +4,7 @@ # # id :bigint not null, primary key # description :text -# hostname :string +# hostname :string not null # port :integer # slug :string not null # title :string not null diff --git a/config/environments/test.rb b/config/environments/test.rb index 171f0fb..f61f93a 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -28,7 +28,7 @@ config.cache_store = :null_store # Raise exceptions instead of rendering exception templates. - config.action_dispatch.show_exceptions = false + config.action_dispatch.show_exceptions = :none # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false diff --git a/db/migrate/20240229063401_create_servers.rb b/db/migrate/20240229063401_create_servers.rb index 83fffa6..3dc966b 100644 --- a/db/migrate/20240229063401_create_servers.rb +++ b/db/migrate/20240229063401_create_servers.rb @@ -4,7 +4,7 @@ def change t.string :title, null: false t.string :slug, null: false t.index :slug, unique: true - t.string :hostname + t.string :hostname, null: false t.integer :port t.text :description diff --git a/db/schema.rb b/db/schema.rb index d9e9b9c..3a886a1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -130,7 +130,7 @@ create_table "servers", force: :cascade do |t| t.string "title", null: false t.string "slug", null: false - t.string "hostname" + t.string "hostname", null: false t.integer "port" t.text "description" t.datetime "created_at", null: false diff --git a/db/seeds.rb b/db/seeds.rb index 9a7f50f..55276be 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,4 +1,3 @@ Dir[Rails.root.join("db/seeds/**/*.rb").to_s].each do |seed| - ap "Seeding Data" load seed end diff --git a/db/seeds/01_required.rb b/db/seeds/01_required.rb index 9045720..ef89d61 100644 --- a/db/seeds/01_required.rb +++ b/db/seeds/01_required.rb @@ -1,3 +1,7 @@ -if Screen.count === 0 - Screen.create title: "Main" +# rubocop:disable Style/SoleNestedConditional +if !Rails.env.test? + if Screen.count == 0 + Screen.create title: "Main" + end end +# rubocop:enable Style/SoleNestedConditional diff --git a/public/404.html b/public/404.html index 2be3af2..41cef8a 100644 --- a/public/404.html +++ b/public/404.html @@ -1,5 +1,5 @@ - + The page you were looking for doesn't exist (404) diff --git a/public/422.html b/public/422.html index c08eac0..c2cfde0 100644 --- a/public/422.html +++ b/public/422.html @@ -1,5 +1,5 @@ - + The change you wanted was rejected (422) diff --git a/public/500.html b/public/500.html index 78a030a..9a07c7b 100644 --- a/public/500.html +++ b/public/500.html @@ -1,5 +1,5 @@ - + We're sorry, but something went wrong (500) diff --git a/spec/factories/commands.rb b/spec/factories/commands.rb index a2f3480..014e24c 100644 --- a/spec/factories/commands.rb +++ b/spec/factories/commands.rb @@ -26,8 +26,31 @@ factory :command do title { "OSC Message" } address { "/test/endpoint" } - payload_type { 0 } + payload_type { :integer } + allow_custom_value { false } server + + trait :with_values do + after(:create) do |command| + create_list(:command_value, 2, command: command) + end + end + + transient do + protocol { nil } + delay { nil } + end + + after(:create) do |command, evaluator| + if evaluator.protocol + create( + :protocols_command, + command: command, + protocol: evaluator.protocol, + delay: evaluator.delay, + ) + end + end end end diff --git a/spec/factories/controls.rb b/spec/factories/controls.rb index cc0557c..121d2f3 100644 --- a/spec/factories/controls.rb +++ b/spec/factories/controls.rb @@ -30,13 +30,17 @@ # FactoryBot.define do factory :control do - title { "MyString" } - type { 1 } - screen { nil } - position { "" } - min_value { "9.99" } - max_value { "9.99" } - value { "9.99" } - protocol { nil } + title { Faker::Company.buzzword } + control_type { :button } + order { nil } # Will be set by callback + screen + + trait :with_protocol do + protocol + end + + trait :with_command do + command + end end end diff --git a/spec/factories/protocols.rb b/spec/factories/protocols.rb index 8524a6e..ba8b822 100644 --- a/spec/factories/protocols.rb +++ b/spec/factories/protocols.rb @@ -15,6 +15,6 @@ # FactoryBot.define do factory :protocol do - title { "MyString" } + title { Faker::Verb.base + " " + Faker::Appliance.equipment } end end diff --git a/spec/factories/protocols_commands.rb b/spec/factories/protocols_commands.rb index c8664a1..2b004e8 100644 --- a/spec/factories/protocols_commands.rb +++ b/spec/factories/protocols_commands.rb @@ -28,6 +28,6 @@ factory :protocols_command do protocol { nil } command { nil } - delay { 1 } + delay { nil } end end diff --git a/spec/factories/roles.rb b/spec/factories/roles.rb deleted file mode 100644 index 8023fbd..0000000 --- a/spec/factories/roles.rb +++ /dev/null @@ -1,21 +0,0 @@ -# == Schema Information -# -# Table name: roles -# -# id :bigint not null, primary key -# name :string -# resource_type :string -# created_at :datetime not null -# updated_at :datetime not null -# resource_id :bigint -# -# Indexes -# -# index_roles_on_name_and_resource_type_and_resource_id (name,resource_type,resource_id) -# index_roles_on_resource (resource_type,resource_id) -# -FactoryBot.define do - factory :role do - - end -end diff --git a/spec/factories/screens.rb b/spec/factories/screens.rb index e95fa6d..3c8b96d 100644 --- a/spec/factories/screens.rb +++ b/spec/factories/screens.rb @@ -15,7 +15,7 @@ # FactoryBot.define do factory :screen do - title { "MyString" } - order { 1 } + title { Faker::Company.bs.split.first } + order { nil } end end diff --git a/spec/factories/servers.rb b/spec/factories/servers.rb index f99b382..1f16825 100644 --- a/spec/factories/servers.rb +++ b/spec/factories/servers.rb @@ -4,7 +4,7 @@ # # id :bigint not null, primary key # description :text -# hostname :string +# hostname :string not null # port :integer # slug :string not null # title :string not null diff --git a/spec/factories/users.rb b/spec/factories/users.rb index bcf578c..351f126 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -48,6 +48,9 @@ # FactoryBot.define do factory :user do - + email { Faker::Internet.email } + password { Faker::Internet.password(min_length: 8, mix_case: true, special_characters: true) } + confirmed_at { Time.current } + active { true } end end diff --git a/spec/jobs/send_osc_command_job_spec.rb b/spec/jobs/send_osc_command_job_spec.rb deleted file mode 100644 index 791e2ad..0000000 --- a/spec/jobs/send_osc_command_job_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'rails_helper' - -RSpec.describe SendOscCommandJob, type: :job do - let(:command) { build_stubbed(:command) } - - describe 'perform' do - it 'works' do - mock_socket = instance_double(UDPSocket) - - allow(UDPSocket).to receive(:new).and_return(mock_socket) - allow(mock_socket).to receive(:bind) - allow(mock_socket).to receive(:connect) - allow(mock_socket).to receive(:recv) - - message = OscService::Message.new(command.address) - - expect(mock_socket).to receive(:send).with(message.encode, 2) # rubocop:disable RSpec/MessageSpies - - udp_receiver = UDPSocket.new - udp_receiver.bind(command.server.hostname, command.server.port) - - SendOscProtocolJob.perform_later(command) - end - end -end diff --git a/spec/jobs/send_osc_commands_job_spec.rb b/spec/jobs/send_osc_commands_job_spec.rb new file mode 100644 index 0000000..c108b0d --- /dev/null +++ b/spec/jobs/send_osc_commands_job_spec.rb @@ -0,0 +1,26 @@ +require "rails_helper" + +RSpec.describe SendOscCommandsJob, type: :job do + describe "perform" do + it "sends an OSC command over the network" do + command = create(:command, server: create(:server)) + + message = OscService::Message.new(command.address, 0) + + mock_socket = instance_double(UDPSocket) + + allow(UDPSocket).to receive(:new).and_return(mock_socket) + allow(mock_socket).to receive(:bind) + allow(mock_socket).to receive(:connect) + allow(mock_socket).to receive(:recv) + allow(mock_socket).to receive(:send).with(message.encode, 2) + + udp_receiver = UDPSocket.new + udp_receiver.bind(command.server.hostname, command.server.port) + + described_class.perform_now(command) + + expect(mock_socket).to have_received(:send).with(message.encode, 2) + end + end +end diff --git a/spec/jobs/send_osc_protocol_job_spec.rb b/spec/jobs/send_osc_protocol_job_spec.rb index 00ee805..638f2e3 100644 --- a/spec/jobs/send_osc_protocol_job_spec.rb +++ b/spec/jobs/send_osc_protocol_job_spec.rb @@ -1,5 +1,67 @@ -require 'rails_helper' +require "rails_helper" RSpec.describe SendOscProtocolJob, type: :job do - pending "add some examples to (or delete) #{__FILE__}" + let(:protocol) { create(:protocol) } + let(:server) { create(:server) } + + describe "#perform" do + context "with commands having different delays" do + it "groups and schedules commands correctly" do + create(:command, protocol: protocol, server: server, delay: 0) + create(:command, protocol: protocol, server: server, delay: 1000) + create(:command, protocol: protocol, server: server, delay: 3000) + + allow(SendOscCommandsJob).to receive(:perform_later) + allow(SendOscCommandsJob).to receive(:set) + .with(wait: 0) + .and_return(SendOscCommandsJob) + allow(SendOscCommandsJob).to receive(:set) + .with(wait: 1) + .and_return(SendOscCommandsJob) + allow(SendOscCommandsJob).to receive(:set) + .with(wait: 4) + .and_return(SendOscCommandsJob) + + described_class.perform_now(protocol) + + expect(SendOscCommandsJob).to have_received(:perform_later) + .exactly(3).times + end + end + + context "with commands for different servers" do + it "groups commands by server" do + server2 = create(:server) + create(:command, protocol: protocol, server: server, delay: 0) + create(:command, protocol: protocol, server: server2, delay: 0) + + allow(SendOscCommandsJob).to receive(:perform_later) + allow(SendOscCommandsJob).to receive(:set) + .with(wait: 0) + .and_return(SendOscCommandsJob) + + described_class.perform_now(protocol) + + expect(SendOscCommandsJob).to have_received(:perform_later) + .twice + end + end + + context "with consecutive commands without delay" do + it "groups commands together" do + create(:command, protocol: protocol, server: server, delay: 0) + create(:command, protocol: protocol, server: server, delay: 0) + + allow(SendOscCommandsJob).to receive(:perform_later) + allow(SendOscCommandsJob).to receive(:set) + .with(wait: 0) + .and_return(SendOscCommandsJob) + + described_class.perform_now(protocol) + + expect(SendOscCommandsJob).to have_received(:perform_later) + .once + end + end + end end diff --git a/spec/models/command_spec.rb b/spec/models/command_spec.rb index d17373a..8202e6a 100644 --- a/spec/models/command_spec.rb +++ b/spec/models/command_spec.rb @@ -25,5 +25,69 @@ require 'rails_helper' RSpec.describe Command, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + expect(build(:command)).to be_valid + end + + it "is invalid with missing attributes" do + %i(title).each do |attr| + expect(build(:command, attr => nil)).not_to be_valid + end + end + end + + describe 'associations' do + it { is_expected.to have_many(:protocols_commands).dependent(:destroy) } + it { is_expected.to have_many(:protocols).through(:protocols_commands) } + it { is_expected.to have_many(:command_values).dependent(:destroy) } + it { is_expected.to belong_to(:server) } + end + + describe 'nested attributes' do + it { is_expected.to accept_nested_attributes_for(:command_values).allow_destroy(true) } + end + + describe 'slug generation' do + it 'generates a slug from the title' do + command = create(:command, title: 'Test Command') + expect(command.slug).to eq('test-command') + end + + it 'ensures slug uniqueness' do + command1 = create(:command, title: 'Test Command') + command2 = create(:command, title: 'Test Command') + + expect(command1.slug).to eq('test-command') + expect(command2.slug).to eq('test-command-1') + end + end + + describe 'command values' do + let(:command) { create(:command) } + + it 'can have multiple command values' do + command.command_values.create([ + { value: '1', label: 'One' }, + { value: '2', label: 'Two' } + ]) + + expect(command.command_values.count).to eq(2) + end + end + + describe 'protocols association' do + let(:command) { create(:command) } + let(:protocol) { create(:protocol) } + + it 'can be associated with multiple protocols' do + command.protocols << protocol + expect(command.protocols).to include(protocol) + end + + it 'creates protocols_command with correct order' do + command.protocols << protocol + expect(command.protocols_commands.first.order).to eq(1) + end + end end diff --git a/spec/models/command_value_spec.rb b/spec/models/command_value_spec.rb index 1f91fd0..f2be7f0 100644 --- a/spec/models/command_value_spec.rb +++ b/spec/models/command_value_spec.rb @@ -20,5 +20,53 @@ require 'rails_helper' RSpec.describe CommandValue, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + command = build_stubbed(:command) + expect(build(:command_value, command: command)).to be_valid + end + + it "is invalid with missing attributes" do + command = build_stubbed(:command) + %i(value).each do |attr| + expect(build(:command_value, attr => nil, command: command)).not_to be_valid + end + end + end + + describe 'associations' do + it { is_expected.to belong_to(:command) } + end + + describe 'command values' do + it 'can be created with a value' do + command = create(:command) + command_value = command.command_values.create(value: 'test_value') + + expect(command_value).to be_persisted + expect(command_value.value).to eq('test_value') + end + + it 'can be created with a label' do + command = create(:command) + command_value = command.command_values.create(value: 'test_value', label: 'Test Label') + + expect(command_value.label).to eq('Test Label') + end + end + + describe 'protocols association' do + it 'can be referenced by protocols_commands' do + command = create(:command) + command_value = create(:command_value, command: command) + protocol = create(:protocol) + + protocols_command = create(:protocols_command, + protocol: protocol, + command: command, + command_value: command_value,) + + expect(protocols_command.command_value).to eq(command_value) + end + end end diff --git a/spec/models/control_spec.rb b/spec/models/control_spec.rb index 1dc32bb..90ba8df 100644 --- a/spec/models/control_spec.rb +++ b/spec/models/control_spec.rb @@ -31,5 +31,96 @@ require 'rails_helper' RSpec.describe Control, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + screen = build_stubbed(:screen) + command = build_stubbed(:command) + expect(build(:control, screen: screen, command: command)).to be_valid + end + + it "is invalid with missing attributes" do + screen = build_stubbed(:screen) + command = build_stubbed(:command) + %i(control_type title).each do |attr| + expect(build(:control, attr => nil, screen: screen, command: command)).not_to be_valid + end + end + end + + describe 'associations' do + it { is_expected.to belong_to(:screen) } + it { is_expected.to belong_to(:protocol).optional } + it { is_expected.to belong_to(:command).optional } + end + + describe 'enums' do + it { is_expected.to define_enum_for(:control_type).with_values(button: 0, slider: 1, spacer: 2) } + end + + describe 'callbacks' do + describe '#set_unique_order' do + it 'sets order to 1 for first control in screen' do + screen = create(:screen) + control = create(:control, :with_command, screen: screen) + expect(control.order).to eq(1) + end + + it 'increments order for subsequent controls' do + screen = create(:screen) + create(:control, :with_command, screen: screen, order: 1) + control = create(:control, :with_command, screen: screen) + expect(control.order).to eq(2) + end + end + + describe '#set_spacer_title' do + it 'sets title for spacer controls' do + screen = create(:screen) + control = create(:control, :with_command, screen: screen, control_type: :spacer) + expect(control.title).to eq("spacer_#{control.order}") + end + + it 'does not modify title for non-spacer controls' do + screen = create(:screen) + control = create(:control, :with_command, screen: screen, control_type: :button, title: 'Test Button') + expect(control.title).to eq('Test Button') + end + end + end + + describe '#protocol_xor_command' do + it 'allows control with protocol but no command' do + protocol = create(:protocol) + control = build(:control, protocol: protocol, command: nil) + control.valid? + expect(control.errors[:protocol_xor_command]).to be_empty + end + + it 'allows control with command but no protocol' do + command = create(:command) + control = build(:control, command: command, protocol: nil) + control.valid? + expect(control.errors[:protocol_xor_command]).to be_empty + end + + it 'does not allow control with both protocol and command' do + protocol = create(:protocol) + command = create(:command) + control = build(:control, protocol: protocol, command: command) + control.valid? + expect(control.errors[:protocol_xor_command]).to include('A control must reference either a protocol or a command') + end + + it 'does not allow control with neither protocol nor command' do + control = build(:control, protocol: nil, command: nil) + control.valid? + expect(control.errors[:protocol_xor_command]).to include('A control must reference either a protocol or a command') + end + + it 'skips validation for spacer controls' do + control = build(:control, control_type: :spacer, protocol: nil, command: nil) + control.valid? + expect(control.errors[:protocol_xor_command]).to be_empty + end + end end diff --git a/spec/models/protocol_spec.rb b/spec/models/protocol_spec.rb index 12abff2..3c4ad40 100644 --- a/spec/models/protocol_spec.rb +++ b/spec/models/protocol_spec.rb @@ -16,5 +16,94 @@ require 'rails_helper' RSpec.describe Protocol, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + expect(build(:protocol)).to be_valid + end + + it "is invalid with missing attributes" do + %i(title).each do |attr| + expect(build(:protocol, attr => nil)).not_to be_valid + end + end + end + + describe 'associations' do + it 'has many protocols_commands' do + protocol = create(:protocol) + command = create(:command) + protocols_command = create(:protocols_command, protocol: protocol, command: command) + + expect(protocol.protocols_commands).to include(protocols_command) + end + + it 'has many commands through protocols_commands' do + protocol = create(:protocol) + command = create(:command) + create(:protocols_command, protocol: protocol, command: command) + + expect(protocol.commands).to include(command) + end + + it 'destroys dependent protocols_commands when deleted' do + protocol = create(:protocol) + command = create(:command) + create(:protocols_command, protocol: protocol, command: command) + + expect { protocol.destroy }.to change(ProtocolsCommand, :count).by(-1) + end + + it 'nullifies associated commands when deleted' do + protocol = create(:protocol) + command = create(:command) + create(:protocols_command, protocol: protocol, command: command) + + protocol.destroy + command.reload + + expect(command.persisted?).to be true + end + end + + describe 'nested attributes' do + it 'accepts nested attributes for protocols_commands' do + protocol = create(:protocol) + command = create(:command) + + protocol.update(protocols_commands_attributes: [{ + command_id: command.id, + value: "test_value" + }]) + + expect(protocol.protocols_commands.first.value).to eq("test_value") + end + + it 'allows destroying protocols_commands through nested attributes' do + protocol = create(:protocol) + command = create(:command) + protocols_command = create(:protocols_command, protocol: protocol, command: command) + + expect { + protocol.update(protocols_commands_attributes: [{ + id: protocols_command.id, + _destroy: '1' + }]) + }.to change(ProtocolsCommand, :count).by(-1) + end + end + + describe 'slug generation' do + it 'generates a slug from the title' do + protocol = create(:protocol, title: 'Test Protocol') + expect(protocol.slug).to eq('test-protocol') + end + + it 'ensures slug uniqueness' do + protocol1 = create(:protocol, title: 'Test Protocol') + protocol2 = create(:protocol, title: 'Test Protocol') + + expect(protocol1.slug).to eq('test-protocol') + expect(protocol2.slug).to eq('test-protocol-1') + end + end end diff --git a/spec/models/protocols_command_spec.rb b/spec/models/protocols_command_spec.rb index 958705d..b799d0f 100644 --- a/spec/models/protocols_command_spec.rb +++ b/spec/models/protocols_command_spec.rb @@ -24,8 +24,105 @@ # fk_rails_... (command_value_id => command_values.id) # fk_rails_... (protocol_id => protocols.id) # -require 'rails_helper' +require "rails_helper" RSpec.describe ProtocolsCommand, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + protocol = build(:protocol) + command = build(:command) + expect(build(:protocols_command, protocol: protocol, command: command)).to be_valid + end + + it "is invalid with missing attributes" do + protocol = build(:protocol) + command = build(:command) + %i(protocol_id command_id).each do |attr| + expect(build(:protocols_command, protocol: protocol, command: command, attr => nil)).not_to be_valid + end + end + end + + describe "associations" do + it { is_expected.to belong_to(:protocol) } + it { is_expected.to belong_to(:command) } + it { is_expected.to belong_to(:command_value).optional } + end + + describe "callbacks" do + describe "#set_command_order" do + it "sets order to 1 for first command in protocol" do + protocol = create(:protocol) + protocols_command = create(:protocols_command, protocol: protocol, command: create(:command)) + expect(protocols_command.order).to eq(1) + end + + it "increments order for subsequent commands" do + protocol = create(:protocol) + command1 = create(:command) + command2 = create(:command) + + first_protocols_command = create(:protocols_command, protocol: protocol, command: command1) + second_protocols_command = create(:protocols_command, protocol: protocol, command: command2) + + expect(first_protocols_command.order).to eq(1) + expect(second_protocols_command.order).to eq(2) + end + + it "maintains existing order if specified" do + protocol = create(:protocol) + protocols_command = create(:protocols_command, protocol: protocol, command: create(:command), order: 5) + expect(protocols_command.order).to eq(5) + end + end + end + + describe "scoping" do + it "orders by order attribute by default" do + protocol = create(:protocol) + command3 = create(:protocols_command, protocol: protocol, command: create(:command), order: 3) + command1 = create(:protocols_command, protocol: protocol, command: create(:command), order: 1) + command2 = create(:protocols_command, protocol: protocol, command: create(:command), order: 2) + + expect(protocol.protocols_commands.to_a).to eq([command1, command2, command3]) + end + end + + describe "command values" do + it "can store a custom value string" do + protocols_command = create(:protocols_command, + protocol: create(:protocol), + command: create(:command), + value: "custom_value",) + expect(protocols_command.value).to eq("custom_value") + end + + it "can reference a command value" do + command = create(:command) + command_value = create(:command_value, command: command) + protocols_command = create(:protocols_command, + protocol: create(:protocol), + command: command, + command_value: command_value,) + expect(protocols_command.command_value).to eq(command_value) + end + end + + describe "delay handling" do + it "allows nil delay" do + protocols_command = create(:protocols_command, + protocol: create(:protocol), + command: create(:command), + delay: nil,) + expect(protocols_command.delay).to be_nil + end + + it "stores integer delay values" do + protocols_command = create(:protocols_command, + protocol: create(:protocol), + command: create(:command), + delay: 1000,) + expect(protocols_command.delay).to eq(1000) + end + end end diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb deleted file mode 100644 index f79c122..0000000 --- a/spec/models/role_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# == Schema Information -# -# Table name: roles -# -# id :bigint not null, primary key -# name :string -# resource_type :string -# created_at :datetime not null -# updated_at :datetime not null -# resource_id :bigint -# -# Indexes -# -# index_roles_on_name_and_resource_type_and_resource_id (name,resource_type,resource_id) -# index_roles_on_resource (resource_type,resource_id) -# -require 'rails_helper' - -RSpec.describe Role, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/screen_spec.rb b/spec/models/screen_spec.rb index 18ae670..3e14f2d 100644 --- a/spec/models/screen_spec.rb +++ b/spec/models/screen_spec.rb @@ -13,8 +13,71 @@ # # index_screens_on_slug (slug) UNIQUE # -require 'rails_helper' +require "rails_helper" RSpec.describe Screen, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + screen = build(:screen) + expect(screen).to be_valid + end + + it "is invalid with missing attributes" do + %i(title).each do |attr| + expect(build(:protocol, attr => nil)).not_to be_valid + end + end + end + + describe "Associations" do + it { is_expected.to have_many(:controls) } + + it "has many controls" do + screen = create(:screen) + control1 = create(:control, :with_command, screen: screen) + control2 = create(:control, :with_command, screen: screen) + screen.reload + + expect(screen.controls).to include(control1, control2) + end + + it "destroys associated controls when destroyed" do + screen = create(:screen) + control = create(:control, :with_command, screen: screen) + + screen.destroy + + expect(Control.exists?(control.id)).to be false + end + end + + describe "Callbacks" do + describe "#set_screen_order" do + it "sets order to 1 for first screen" do + screen = create(:screen) + expect(screen.order).to eq(1) + end + + it "increments order for subsequent screens" do + first_screen = create(:screen) + second_screen = create(:screen) + expect(second_screen.order).to eq(first_screen.order + 1) + end + + it "doesn't change existing order" do + screen = create(:screen, order: 5) + expect(screen.order).to eq(5) + end + end + end + + describe "Scopes" do + it "orders by order attribute by default" do + screen3 = create(:screen, order: 3) + screen1 = create(:screen, order: 1) + screen2 = create(:screen, order: 2) + + expect(described_class.all).to eq([screen1, screen2, screen3]) + end + end end diff --git a/spec/models/server_spec.rb b/spec/models/server_spec.rb index dc51212..9e180ff 100644 --- a/spec/models/server_spec.rb +++ b/spec/models/server_spec.rb @@ -4,7 +4,7 @@ # # id :bigint not null, primary key # description :text -# hostname :string +# hostname :string not null # port :integer # slug :string not null # title :string not null @@ -15,8 +15,24 @@ # # index_servers_on_slug (slug) UNIQUE # -require 'rails_helper' +require "rails_helper" RSpec.describe Server, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "validations" do + it "is valid with valid attributes" do + server = build(:server) + expect(server).to be_valid + end + + it "is invalid with missing attributes" do + %i(title).each do |attr| + expect(build(:server, attr => nil)).not_to be_valid + end + end + end + + describe "associations" do + it { is_expected.to have_many(:commands) } + end + end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 88325d4..47a87a4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -46,8 +46,48 @@ # index_users_on_unlock_token (unlock_token) UNIQUE # index_users_on_user_preferences (user_preferences) USING gin # -require 'rails_helper' +require "rails_helper" RSpec.describe User, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "Validations" do + it "is valid with valid attributes" do + expect(build(:user)).to be_valid + end + + it "is invalid with missing attributes" do + %i(email password).each do |attr| + expect(build(:user, attr => nil)).not_to be_valid + end + end + + it "validates email format" do + expect(build(:user, email: "invalid-email")).not_to be_valid + expect(build(:user, email: "valid@example.com")).to be_valid + end + + it "validates email length" do + expect(build(:user, email: "#{('a' * 250)}@example.com")).not_to be_valid + end + + it "validates password complexity" do + invalid_passwords = ["simple", "UPPERCASE", "lowercase", "12345678", "!@#$%^&*"] + invalid_passwords.each do |password| + expect(build(:user, password: password)).not_to be_valid + end + + expect(build(:user, password: "Valid1Password!")).to be_valid + end + end + + describe "#limit" do + it "returns table preferences limit for given model" do + user = build(:user, table_preferences: { "User" => { "limit" => 25 } }) + expect(user.limit("User")).to eq(25) + end + + it "returns nil when no limit is set" do + user = build(:user) + expect(user.limit("User")).to be_nil + end + end end diff --git a/spec/policies/server_policy_spec.rb b/spec/policies/server_policy_spec.rb deleted file mode 100644 index 9e76217..0000000 --- a/spec/policies/server_policy_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'rails_helper' - -RSpec.describe ServerPolicy, type: :policy do - let(:user) { User.new } - - subject { described_class } - - permissions ".scope" do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :show? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :create? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :update? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :destroy? do - pending "add some examples to (or delete) #{__FILE__}" - end -end diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb deleted file mode 100644 index 7b6d62c..0000000 --- a/spec/policies/user_policy_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' - -RSpec.describe UserPolicy, type: :policy do - let(:user) { User.new } - - subject { described_class } - - permissions ".scope" do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :show? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :create? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :update? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :destroy? do - pending "add some examples to (or delete) #{__FILE__}" - end -end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 292b8be..c95701c 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,15 +1,15 @@ -require 'spec_helper' -ENV['RAILS_ENV'] ||= 'test' -require_relative '../config/environment' +require "spec_helper" +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? -require 'rspec/rails' +require "rspec/rails" # Add additional requires below this line. Rails is not loaded until this point! -require 'inertia_rails/rspec' -require 'bullet' -require 'database_cleaner/active_record' +require "inertia_rails/rspec" +require "bullet" +require "database_cleaner/active_record" require "pundit/rspec" # Requires supporting ruby files with custom matchers and macros, etc, in @@ -37,7 +37,7 @@ RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{Rails.root.join('spec/fixtures')}" + # config.fixture_paths = ["#{Rails.root.join('spec/fixtures')}"] # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false diff --git a/spec/requests/api/commands_spec.rb b/spec/requests/api/commands_spec.rb index 504a0e5..c55e658 100644 --- a/spec/requests/api/commands_spec.rb +++ b/spec/requests/api/commands_spec.rb @@ -1,6 +1,9 @@ -require 'rails_helper' +require "rails_helper" +require_relative "../../support/devise" RSpec.describe "Api::Commands" do + login_user(:admin) + describe "GET /payload_types" do it "returns a list of payload types" do get api_commands_payload_types_url diff --git a/spec/requests/api/controls_spec.rb b/spec/requests/api/controls_spec.rb index 8e91077..2ceac93 100644 --- a/spec/requests/api/controls_spec.rb +++ b/spec/requests/api/controls_spec.rb @@ -1,7 +1,64 @@ -require 'rails_helper' +require "rails_helper" +require_relative "../../support/devise" RSpec.describe "Api::Controls", type: :request do - describe "GET /index" do - pending "add some examples (or delete) #{__FILE__}" + login_user(:admin) + + describe "GET /api/controls/options" do + it "returns a successful response" do + get api_controls_options_url + expect(response).to be_successful + end + end + + describe "POST /api/controls" do + it "creates a new Control with valid parameters" do + screen = create(:screen) + command = create(:command) + + expect { + post api_controls_url, params: { control: attributes_for(:control, :with_command, command_id: command.id, screen_id: screen.id) } + }.to change(Control, :count).by(1) + + expect(response).to have_http_status(:created) + end + + it "fails to create Control with invalid parameters" do + screen = create(:screen) + command = create(:command) + + expect { + post api_controls_url, params: { control: attributes_for(:control, :with_command, title: nil, command_id: command.id, screen_id: screen.id) } + }.not_to change(Control, :count) + + expect(response).to have_http_status(:not_acceptable) + end + end + + describe "PATCH /api/controls/:id" do + it "updates the control with valid parameters" do + screen = create(:screen) + command = create(:command) + control = create(:control, :with_command, command_id: command.id, screen_id: screen.id) + + patch api_control_url(control), params: { control: { title: "New Title" } } + + control.reload + expect(control.title).to eq("New Title") + expect(response).to have_http_status(:created) + end + + it "fails to update control with invalid parameters" do + screen = create(:screen) + command = create(:command) + control = create(:control, :with_command, command_id: command.id, screen_id: screen.id) + original_title = control.title + + patch api_control_url(control), params: { control: { title: nil } } + + control.reload + expect(control.title).to eq(original_title) + expect(response).to have_http_status(:not_acceptable) + end end end diff --git a/spec/requests/api/protocols_spec.rb b/spec/requests/api/protocols_spec.rb index 656c951..4ee5198 100644 --- a/spec/requests/api/protocols_spec.rb +++ b/spec/requests/api/protocols_spec.rb @@ -1,7 +1,132 @@ -require 'rails_helper' +require "rails_helper" +require_relative "../../support/devise" RSpec.describe "Api::Protocols", type: :request do - describe "GET /index" do - pending "add some examples (or delete) #{__FILE__}" + login_user(:admin) + + describe "GET /api/protocols/options #options" do + it "returns a successful response" do + get api_protocols_options_url + expect(response).to be_successful + end + + it "returns protocols in options format" do + protocol = create(:protocol) + get api_protocols_options_url + expect(response.parsed_body).to include( + hash_including("id" => protocol.id, "title" => protocol.title), + ) + end + end + + describe "GET /api/protocols/:slug #show" do + it "returns a successful response" do + protocol = create(:protocol) + get api_protocol_url(protocol) + expect(response).to be_successful + end + + it "returns the protocol data" do + protocol = create(:protocol) + get api_protocol_url(protocol) + expect(response.parsed_body).to include( + "id" => protocol.id, + "title" => protocol.title, + ) + end + end + + describe "POST /api/protocols/:slug/execute #execute" do + it "returns accepted status when executing protocol" do + protocol = create(:protocol) + put api_execute_protocol_url(protocol) + + expect(response).to have_http_status(:accepted) + end + + it "creates an activity record" do + protocol = create(:protocol) + + expect { + put api_execute_protocol_url(protocol) + }.to change { + PublicActivity::Activity.where( + trackable: protocol, + key: "protocol.execute", + ).count + }.by(1) + end + + it "enqueues an OSC protocol job" do + protocol = create(:protocol) + expect { + put api_execute_protocol_url(protocol) + }.to have_enqueued_job(SendOscProtocolJob).with(protocol) + end + end + + describe "POST /api/protocols #create" do + it "creates a new Protocol with valid parameters" do + valid_attributes = attributes_for(:protocol) + expect { + post api_protocols_url, params: { protocol: valid_attributes } + }.to change(Protocol, :count).by(1) + end + + it "returns created status with valid parameters" do + valid_attributes = attributes_for(:protocol) + post api_protocols_url, params: { protocol: valid_attributes } + + expect(response).to have_http_status(:created) + end + + it "does not create a new Protocol with invalid parameters" do + invalid_attributes = attributes_for(:protocol, title: nil) + + expect { + post api_protocols_url, params: { protocol: invalid_attributes } + }.not_to change(Protocol, :count) + end + + it "returns not acceptable status with invalid parameters" do + invalid_attributes = attributes_for(:protocol, title: nil) + post api_protocols_url, params: { protocol: invalid_attributes } + + expect(response).to have_http_status(:not_acceptable) + end + end + + describe "PATCH /api/protocols/:slug #update" do + it "updates the requested protocol with valid parameters" do + protocol = create(:protocol) + + patch api_protocol_url(protocol), params: { protocol: { title: "New Title" } } + protocol.reload + expect(protocol.title).to eq("New Title") + end + + it "returns created status with valid parameters" do + protocol = create(:protocol) + + patch api_protocol_url(protocol), params: { protocol: { title: "New Title" } } + expect(response).to have_http_status(:created) + end + + it "does not update the protocol with invalid parameters" do + protocol = create(:protocol) + original_title = protocol.title + + patch api_protocol_url(protocol), params: { protocol: { title: nil } } + protocol.reload + expect(protocol.title).to eq(original_title) + end + + it "returns not acceptable status with invalid parameters" do + protocol = create(:protocol) + invalid_attributes = attributes_for(:protocol, title: nil) + + patch api_protocol_url(protocol), params: { protocol: invalid_attributes } + expect(response).to have_http_status(:not_acceptable) + end end end diff --git a/spec/requests/api/servers_spec.rb b/spec/requests/api/servers_spec.rb index 789afbe..f4b16b0 100644 --- a/spec/requests/api/servers_spec.rb +++ b/spec/requests/api/servers_spec.rb @@ -1,7 +1,21 @@ -require 'rails_helper' +require "rails_helper" +require_relative "../../support/devise" RSpec.describe "Api::Servers", type: :request do + login_user(:admin) + describe "GET /index" do - pending "add some examples (or delete) #{__FILE__}" + it "returns a successful response" do + get api_servers_url + expect(response).to be_successful + end + + it "returns a list of servers" do + server = create(:server) + get api_servers_url + expect(response.parsed_body).to include( + hash_including("id" => server.id, "title" => server.title), + ) + end end end diff --git a/spec/requests/commands_spec.rb b/spec/requests/commands_spec.rb index 097510b..e9ce6e5 100644 --- a/spec/requests/commands_spec.rb +++ b/spec/requests/commands_spec.rb @@ -1,33 +1,12 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. +require "rails_helper" +require_relative "../support/devise" RSpec.describe "/commands", type: :request do - - # This should return the minimal set of attributes required to create a valid - # Command. As you add validations to Command, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } + login_user(:admin) describe "GET /index" do it "renders a successful response" do - Command.create! valid_attributes + create(:command) get commands_url expect(response).to be_successful end @@ -35,7 +14,7 @@ describe "GET /show" do it "renders a successful response" do - command = Command.create! valid_attributes + command = create(:command) get command_url(command) expect(response).to be_successful end @@ -50,7 +29,7 @@ describe "GET /edit" do it "renders a successful response" do - command = Command.create! valid_attributes + command = create(:command) get edit_command_url(command) expect(response).to be_successful end @@ -59,59 +38,67 @@ describe "POST /create" do context "with valid parameters" do it "creates a new Command" do + server = create(:server) expect { - post commands_url, params: { command: valid_attributes } + post commands_url, params: { command: attributes_for(:command, server_id: server.id) } }.to change(Command, :count).by(1) end - it "redirects to the created command" do - post commands_url, params: { command: valid_attributes } - expect(response).to redirect_to(command_url(Command.last)) + it "redirects to the commands index page" do + server = create(:server) + attributes = attributes_for(:command) + attributes[:server_id] = server.id + + post commands_url, params: { command: attributes } + + expect(response).to redirect_to(commands_url) end end context "with invalid parameters" do it "does not create a new Command" do expect { - post commands_url, params: { command: invalid_attributes } + post commands_url, params: { command: attributes_for(:command, title: nil) } }.not_to change(Command, :count) end - it "renders a response with 422 status (i.e. to display the 'new' template)" do - post commands_url, params: { command: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the new command page" do + post commands_url, params: { command: attributes_for(:command, title: nil) } + expect(response).to redirect_to(new_command_url) end - end end describe "PATCH /update" do context "with valid parameters" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - it "updates the requested command" do - command = Command.create! valid_attributes - patch command_url(command), params: { command: new_attributes } + server = create(:server) + command = create(:command, server_id: server.id) + patch command_url(command), params: { command: attributes_for(:command, title: "New Name") } command.reload - skip("Add assertions for updated state") + + expect(command.title).to eq("New Name") end - it "redirects to the command" do - command = Command.create! valid_attributes - patch command_url(command), params: { command: new_attributes } + it "redirects to the commands index page" do + server = create(:server) + command = create(:command, server_id: server.id) + patch command_url(command), params: { command: attributes_for(:command, title: "New Name") } command.reload - expect(response).to redirect_to(command_url(command)) + + expect(response).to redirect_to(commands_url) end end context "with invalid parameters" do - it "renders a response with 422 status (i.e. to display the 'edit' template)" do - command = Command.create! valid_attributes - patch command_url(command), params: { command: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the edit command page" do + server = create(:server) + command = create(:command, server_id: server.id) + + patch command_url(command), params: { command: attributes_for(:command, title: nil) } + + expect(response).to redirect_to(edit_command_url(command)) end end @@ -119,14 +106,16 @@ describe "DELETE /destroy" do it "destroys the requested command" do - command = Command.create! valid_attributes + server = create(:server) + command = create(:command, server_id: server.id) expect { delete command_url(command) }.to change(Command, :count).by(-1) end it "redirects to the commands list" do - command = Command.create! valid_attributes + server = create(:server) + command = create(:command, server_id: server.id) delete command_url(command) expect(response).to redirect_to(commands_url) end diff --git a/spec/requests/controls_spec.rb b/spec/requests/controls_spec.rb index 9068f9e..307cf6c 100644 --- a/spec/requests/controls_spec.rb +++ b/spec/requests/controls_spec.rb @@ -1,135 +1,91 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. +require "rails_helper" +require_relative "../support/devise" RSpec.describe "/controls", type: :request do - - # This should return the minimal set of attributes required to create a valid - # Control. As you add validations to Control, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } - - describe "GET /index" do - it "renders a successful response" do - Control.create! valid_attributes - get controls_url - expect(response).to be_successful - end - end - - describe "GET /show" do - it "renders a successful response" do - control = Control.create! valid_attributes - get control_url(control) - expect(response).to be_successful - end - end - - describe "GET /new" do - it "renders a successful response" do - get new_control_url - expect(response).to be_successful - end - end - - describe "GET /edit" do - it "renders a successful response" do - control = Control.create! valid_attributes - get edit_control_url(control) - expect(response).to be_successful - end - end + login_user(:admin) describe "POST /create" do context "with valid parameters" do it "creates a new Control" do + + screen = create(:screen) + command = create(:command) + attributes = attributes_for(:control, screen_id: screen.id) + attributes[:command_id] = command.id + expect { - post controls_url, params: { control: valid_attributes } + post controls_url, params: { control: attributes } }.to change(Control, :count).by(1) + end - it "redirects to the created control" do - post controls_url, params: { control: valid_attributes } - expect(response).to redirect_to(control_url(Control.last)) + it "redirects to the edit screen page" do + screen = create(:screen) + command = create(:command) + attributes = attributes_for(:control, screen_id: screen.id) + attributes[:command_id] = command.id + + post controls_url, params: { control: attributes } + expect(response).to redirect_to(edit_screen_url(screen)) end end context "with invalid parameters" do - it "does not create a new Control" do - expect { - post controls_url, params: { control: invalid_attributes } - }.to change(Control, :count).by(0) + it "redirects back to the edit screen page" do + screen = create(:screen) + command = create(:command) + attributes = attributes_for(:control, title: nil, screen_id: screen.id) + attributes[:command_id] = command.id + + post controls_url, params: { control: attributes } + expect(response).to redirect_to(edit_screen_url(screen)) end - - - it "renders a response with 422 status (i.e. to display the 'new' template)" do - post controls_url, params: { control: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) - end - end end describe "PATCH /update" do context "with valid parameters" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - it "updates the requested control" do - control = Control.create! valid_attributes - patch control_url(control), params: { control: new_attributes } + screen = create(:screen) + control = create(:control, :with_command, screen_id: screen.id) + patch control_url(control), params: { control: attributes_for(:control, title: "New Title") } control.reload - skip("Add assertions for updated state") + expect(control.title).to eq("New Title") end it "redirects to the control" do - control = Control.create! valid_attributes - patch control_url(control), params: { control: new_attributes } + screen = create(:screen) + control = create(:control, :with_command, screen_id: screen.id) + patch control_url(control), params: { control: attributes_for(:control, title: "New Title") } control.reload - expect(response).to redirect_to(control_url(control)) + expect(response).to redirect_to(edit_screen_url(screen)) end end context "with invalid parameters" do - - it "renders a response with 422 status (i.e. to display the 'edit' template)" do - control = Control.create! valid_attributes - patch control_url(control), params: { control: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the edit screen page" do + screen = create(:screen) + control = create(:control, :with_command, screen_id: screen.id) + patch control_url(control), params: { control: attributes_for(:control, title: nil) } + expect(response).to redirect_to(edit_screen_url(screen)) end - end end describe "DELETE /destroy" do it "destroys the requested control" do - control = Control.create! valid_attributes + screen = create(:screen) + control = create(:control, :with_command, screen_id: screen.id) expect { delete control_url(control) }.to change(Control, :count).by(-1) end it "redirects to the controls list" do - control = Control.create! valid_attributes + screen = create(:screen) + control = create(:control, :with_command, screen_id: screen.id) delete control_url(control) - expect(response).to redirect_to(controls_url) + expect(response).to redirect_to(edit_screen_url(screen)) end end end diff --git a/spec/requests/pages_spec.rb b/spec/requests/pages_spec.rb deleted file mode 100644 index 9ca99f2..0000000 --- a/spec/requests/pages_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'rails_helper' - -RSpec.describe "Pages", type: :request do - describe "GET /index" do - pending "add some examples (or delete) #{__FILE__}" - end -end diff --git a/spec/requests/protocols_commands_spec.rb b/spec/requests/protocols_commands_spec.rb deleted file mode 100644 index 7c3b839..0000000 --- a/spec/requests/protocols_commands_spec.rb +++ /dev/null @@ -1,134 +0,0 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. - -RSpec.describe "/protocols_commands", type: :request do - - # This should return the minimal set of attributes required to create a valid - # ProtocolsCommand. As you add validations to ProtocolsCommand, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } - - describe "GET /index" do - it "renders a successful response" do - ProtocolsCommand.create! valid_attributes - get protocols_commands_url - expect(response).to be_successful - end - end - - describe "GET /show" do - it "renders a successful response" do - protocols_command = ProtocolsCommand.create! valid_attributes - get protocols_command_url(protocols_command) - expect(response).to be_successful - end - end - - describe "GET /new" do - it "renders a successful response" do - get new_protocols_command_url - expect(response).to be_successful - end - end - - describe "GET /edit" do - it "renders a successful response" do - protocols_command = ProtocolsCommand.create! valid_attributes - get edit_protocols_command_url(protocols_command) - expect(response).to be_successful - end - end - - describe "POST /create" do - context "with valid parameters" do - it "creates a new ProtocolsCommand" do - expect { - post protocols_commands_url, params: { protocols_command: valid_attributes } - }.to change(ProtocolsCommand, :count).by(1) - end - - it "redirects to the created protocols_command" do - post protocols_commands_url, params: { protocols_command: valid_attributes } - expect(response).to redirect_to(protocols_command_url(ProtocolsCommand.last)) - end - end - - context "with invalid parameters" do - it "does not create a new ProtocolsCommand" do - expect { - post protocols_commands_url, params: { protocols_command: invalid_attributes } - }.not_to change(ProtocolsCommand, :count) - end - - it "renders a response with 422 status (i.e. to display the 'new' template)" do - post protocols_commands_url, params: { protocols_command: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) - end - - end - end - - describe "PATCH /update" do - context "with valid parameters" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - - it "updates the requested protocols_command" do - protocols_command = ProtocolsCommand.create! valid_attributes - patch protocols_command_url(protocols_command), params: { protocols_command: new_attributes } - protocols_command.reload - skip("Add assertions for updated state") - end - - it "redirects to the protocols_command" do - protocols_command = ProtocolsCommand.create! valid_attributes - patch protocols_command_url(protocols_command), params: { protocols_command: new_attributes } - protocols_command.reload - expect(response).to redirect_to(protocols_command_url(protocols_command)) - end - end - - context "with invalid parameters" do - - it "renders a response with 422 status (i.e. to display the 'edit' template)" do - protocols_command = ProtocolsCommand.create! valid_attributes - patch protocols_command_url(protocols_command), params: { protocols_command: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) - end - - end - end - - describe "DELETE /destroy" do - it "destroys the requested protocols_command" do - protocols_command = ProtocolsCommand.create! valid_attributes - expect { - delete protocols_command_url(protocols_command) - }.to change(ProtocolsCommand, :count).by(-1) - end - - it "redirects to the protocols_commands list" do - protocols_command = ProtocolsCommand.create! valid_attributes - delete protocols_command_url(protocols_command) - expect(response).to redirect_to(protocols_commands_url) - end - end -end diff --git a/spec/requests/protocols_spec.rb b/spec/requests/protocols_spec.rb index 79bd656..1fbe006 100644 --- a/spec/requests/protocols_spec.rb +++ b/spec/requests/protocols_spec.rb @@ -1,33 +1,12 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. +require "rails_helper" +require_relative "../support/devise" RSpec.describe "/protocols", type: :request do - - # This should return the minimal set of attributes required to create a valid - # Protocol. As you add validations to Protocol, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } + login_user(:admin) describe "GET /index" do it "renders a successful response" do - Protocol.create! valid_attributes + create(:protocol) get protocols_url expect(response).to be_successful end @@ -35,7 +14,7 @@ describe "GET /show" do it "renders a successful response" do - protocol = Protocol.create! valid_attributes + protocol = create(:protocol) get protocol_url(protocol) expect(response).to be_successful end @@ -50,7 +29,7 @@ describe "GET /edit" do it "renders a successful response" do - protocol = Protocol.create! valid_attributes + protocol = create(:protocol) get edit_protocol_url(protocol) expect(response).to be_successful end @@ -60,12 +39,12 @@ context "with valid parameters" do it "creates a new Protocol" do expect { - post protocols_url, params: { protocol: valid_attributes } + post protocols_url, params: { protocol: attributes_for(:protocol) } }.to change(Protocol, :count).by(1) end it "redirects to the created protocol" do - post protocols_url, params: { protocol: valid_attributes } + post protocols_url, params: { protocol: attributes_for(:protocol) } expect(response).to redirect_to(protocol_url(Protocol.last)) end end @@ -73,13 +52,13 @@ context "with invalid parameters" do it "does not create a new Protocol" do expect { - post protocols_url, params: { protocol: invalid_attributes } + post protocols_url, params: { protocol: attributes_for(:protocol, title: nil) } }.not_to change(Protocol, :count) end - it "renders a response with 422 status (i.e. to display the 'new' template)" do - post protocols_url, params: { protocol: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the new protocol page" do + post protocols_url, params: { protocol: attributes_for(:protocol, title: nil) } + expect(response).to redirect_to(new_protocol_url) end end @@ -87,31 +66,26 @@ describe "PATCH /update" do context "with valid parameters" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - it "updates the requested protocol" do - protocol = Protocol.create! valid_attributes - patch protocol_url(protocol), params: { protocol: new_attributes } + protocol = create(:protocol) + patch protocol_url(protocol), params: { protocol: attributes_for(:protocol, title: "New Title") } protocol.reload - skip("Add assertions for updated state") + expect(protocol.title).to eq("New Title") end it "redirects to the protocol" do - protocol = Protocol.create! valid_attributes - patch protocol_url(protocol), params: { protocol: new_attributes } + protocol = create(:protocol) + patch protocol_url(protocol), params: { protocol: attributes_for(:protocol, title: "New Title") } protocol.reload expect(response).to redirect_to(protocol_url(protocol)) end end context "with invalid parameters" do - - it "renders a response with 422 status (i.e. to display the 'edit' template)" do - protocol = Protocol.create! valid_attributes - patch protocol_url(protocol), params: { protocol: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the edit protocol page" do + protocol = create(:protocol) + patch protocol_url(protocol), params: { protocol: attributes_for(:protocol, title: nil) } + expect(response).to redirect_to(edit_protocol_url(protocol)) end end @@ -119,14 +93,14 @@ describe "DELETE /destroy" do it "destroys the requested protocol" do - protocol = Protocol.create! valid_attributes + protocol = create(:protocol) expect { delete protocol_url(protocol) }.to change(Protocol, :count).by(-1) end it "redirects to the protocols list" do - protocol = Protocol.create! valid_attributes + protocol = create(:protocol) delete protocol_url(protocol) expect(response).to redirect_to(protocols_url) end diff --git a/spec/requests/screens_spec.rb b/spec/requests/screens_spec.rb index aff32ee..8494361 100644 --- a/spec/requests/screens_spec.rb +++ b/spec/requests/screens_spec.rb @@ -1,41 +1,31 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. +require "rails_helper" +require_relative "../support/devise" RSpec.describe "/screens", type: :request do + login_user(:admin) - # This should return the minimal set of attributes required to create a valid - # Screen. As you add validations to Screen, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } + describe "GET /index" do + context "when screens exist" do + it "redirects to the first ordered screen" do + main_screen = create(:screen, order: 1) + create(:screen, order: 2) - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } + get screens_url + expect(response).to redirect_to(main_screen) + end + end - describe "GET /index" do - it "renders a successful response" do - Screen.create! valid_attributes - get screens_url - expect(response).to be_successful + context "when no screens exist" do + it "redirects to new screen path" do + get screens_url + expect(response).to redirect_to(new_screen_path) + end end end describe "GET /show" do it "renders a successful response" do - screen = Screen.create! valid_attributes + screen = create(:screen) get screen_url(screen) expect(response).to be_successful end @@ -43,7 +33,7 @@ describe "GET /edit" do it "renders a successful response" do - screen = Screen.create! valid_attributes + screen = create(:screen) get edit_screen_url(screen) expect(response).to be_successful end @@ -53,58 +43,54 @@ context "with valid parameters" do it "creates a new Screen" do expect { - post screens_url, params: { screen: valid_attributes } + post screens_url, params: { screen: attributes_for(:screen) } }.to change(Screen, :count).by(1) end - it "redirects to the created screen" do - post screens_url, params: { screen: valid_attributes } - expect(response).to redirect_to(screen_url(Screen.last)) + it "redirects to the edit screen page" do + post screens_url, params: { screen: attributes_for(:screen) } + expect(response).to redirect_to(edit_screen_url(Screen.last)) end end context "with invalid parameters" do it "does not create a new Screen" do expect { - post screens_url, params: { screen: invalid_attributes } + post screens_url, params: { screen: attributes_for(:screen, title: nil) } }.not_to change(Screen, :count) end - it "renders a response with 422 status (i.e. to display the 'new' template)" do - post screens_url, params: { screen: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the new screen page" do + post screens_url, params: { screen: attributes_for(:screen, title: nil) } + expect(response).to redirect_to(new_screen_url) end - end end describe "PATCH /update" do context "with valid parameters" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - it "updates the requested screen" do - screen = Screen.create! valid_attributes - patch screen_url(screen), params: { screen: new_attributes } + screen = create(:screen) + patch screen_url(screen), params: { screen: { title: "New Title" } } screen.reload - skip("Add assertions for updated state") + + expect(screen.title).to eq("New Title") end it "redirects to the screen" do - screen = Screen.create! valid_attributes - patch screen_url(screen), params: { screen: new_attributes } + screen = create(:screen) + patch screen_url(screen), params: { screen: { title: "New Title" } } screen.reload - expect(response).to redirect_to(screen_url(screen)) + expect(response).to redirect_to(edit_screen_url(screen)) end end context "with invalid parameters" do - it "renders a response with 422 status (i.e. to display the 'edit' template)" do - screen = Screen.create! valid_attributes - patch screen_url(screen), params: { screen: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the edit screen page" do + screen = create(:screen) + patch screen_url(screen), params: { screen: { title: "" } } + expect(response).to redirect_to(edit_screen_url(screen)) end end @@ -112,16 +98,16 @@ describe "DELETE /destroy" do it "destroys the requested screen" do - screen = Screen.create! valid_attributes + screen = create(:screen) expect { delete screen_url(screen) }.to change(Screen, :count).by(-1) end - it "redirects to the screens list" do - screen = Screen.create! valid_attributes + it "redirects to the edit screens page" do + screen = create(:screen) delete screen_url(screen) - expect(response).to redirect_to(screens_url) + expect(response).to redirect_to(edit_screens_url) end end end diff --git a/spec/requests/servers_spec.rb b/spec/requests/servers_spec.rb index c29089b..7c61728 100644 --- a/spec/requests/servers_spec.rb +++ b/spec/requests/servers_spec.rb @@ -1,33 +1,12 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. +require "rails_helper" +require_relative "../support/devise" RSpec.describe "/servers", type: :request do - - # This should return the minimal set of attributes required to create a valid - # Server. As you add validations to Server, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } + login_user(:admin) describe "GET /index" do it "renders a successful response" do - Server.create! valid_attributes + create(:server) get servers_url expect(response).to be_successful end @@ -35,7 +14,7 @@ describe "GET /show" do it "renders a successful response" do - server = Server.create! valid_attributes + server = create(:server) get server_url(server) expect(response).to be_successful end @@ -50,7 +29,7 @@ describe "GET /edit" do it "renders a successful response" do - server = Server.create! valid_attributes + server = create(:server) get edit_server_url(server) expect(response).to be_successful end @@ -60,12 +39,12 @@ context "with valid parameters" do it "creates a new Server" do expect { - post servers_url, params: { server: valid_attributes } + post servers_url, params: { server: attributes_for(:server) } }.to change(Server, :count).by(1) end it "redirects to the created server" do - post servers_url, params: { server: valid_attributes } + post servers_url, params: { server: attributes_for(:server) } expect(response).to redirect_to(server_url(Server.last)) end end @@ -73,61 +52,54 @@ context "with invalid parameters" do it "does not create a new Server" do expect { - post servers_url, params: { server: invalid_attributes } - }.to change(Server, :count).by(0) + post servers_url, params: { server: attributes_for(:server, hostname: nil) } + }.not_to change(Server, :count) end - - it "renders a response with 422 status (i.e. to display the 'new' template)" do - post servers_url, params: { server: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the new server page" do + post servers_url, params: { server: attributes_for(:server, hostname: nil) } + expect(response).to redirect_to(new_server_url) end - + end end describe "PATCH /update" do context "with valid parameters" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - it "updates the requested server" do - server = Server.create! valid_attributes - patch server_url(server), params: { server: new_attributes } + server = create(:server) + patch server_url(server), params: { server: attributes_for(:server, hostname: "new.example.com") } server.reload - skip("Add assertions for updated state") + expect(server.hostname).to eq("new.example.com") end it "redirects to the server" do - server = Server.create! valid_attributes - patch server_url(server), params: { server: new_attributes } + server = create(:server) + patch server_url(server), params: { server: attributes_for(:server, hostname: "new.example.com") } server.reload expect(response).to redirect_to(server_url(server)) end end context "with invalid parameters" do - - it "renders a response with 422 status (i.e. to display the 'edit' template)" do - server = Server.create! valid_attributes - patch server_url(server), params: { server: invalid_attributes } - expect(response).to have_http_status(:unprocessable_entity) + it "redirects back to the edit server page" do + server = create(:server) + patch server_url(server), params: { server: attributes_for(:server, hostname: nil) } + expect(response).to redirect_to(edit_server_url(server)) end - end end describe "DELETE /destroy" do it "destroys the requested server" do - server = Server.create! valid_attributes + server = create(:server) expect { delete server_url(server) }.to change(Server, :count).by(-1) end it "redirects to the servers list" do - server = Server.create! valid_attributes + server = create(:server) delete server_url(server) expect(response).to redirect_to(servers_url) end diff --git a/spec/services/osc_service_spec.rb b/spec/services/osc_service_spec.rb index fc56b7b..37c1f65 100644 --- a/spec/services/osc_service_spec.rb +++ b/spec/services/osc_service_spec.rb @@ -1,11 +1,11 @@ -require 'rails_helper' -require 'socket' +require "rails_helper" +require "socket" RSpec.describe OscService do let(:command) { build(:command) } - describe '#send' do - it 'sends message via UDP' do + describe "#send" do + it "sends message via UDP" do mock_socket = instance_double(UDPSocket) allow(UDPSocket).to receive(:new).and_return(mock_socket) diff --git a/spec/support/request_macros.rb b/spec/support/request_macros.rb index e80a16a..6b3ef8f 100644 --- a/spec/support/request_macros.rb +++ b/spec/support/request_macros.rb @@ -1,19 +1,8 @@ module RequestMacros - def login_admin + def login_user(role) before do - @admin = FactoryBot.create(:user) - @admin.confirm - @admin.add_role(:super_admin) - @admin.add_role(:admin, Company.first) - sign_in @admin - end - end - - def login_user - before do - @user = FactoryBot.create(:user) - @user.confirm - @user.add_role(:admin, Company.first) + @user = FactoryBot.create(:user, confirmed_at: Time.current) + @user.add_role(role) sign_in @user end end