-- A simple list of elements

local Common = script.Parent.Parent.Common

local CommonIncludes = Common.Includes
local Roact = require(CommonIncludes.RoactRodux.Roact)

local CommonComponents = Common.Components
local ConnectTheme = require(CommonComponents.ConnectTheme)

local StandardComponents = CommonComponents.StandardComponents
local StandardScrollingFrame = require(StandardComponents.ScrollingFrame)
local StandardTextLabel = require(StandardComponents.TextLabel)
local StandardUIListLayout = require(StandardComponents.UIListLayout)
local StandardUIPadding = require(StandardComponents.UIPadding)

---

--[[
    props

        AnchorPoint?
        Position?
        Size?
        TextSize?

        itemHeight: number
        itemPadding: number?

        sections: array<{
            name: string,

            items: array<{
                name: string,
                onActivated: () -> nil,

                LayoutOrder: number?
                [Roact.Children]: dictionary<any, Element>?
            }>
        }>

    store props

        theme: StudioTheme
]]

local SimpleList = Roact.PureComponent:extend("SimpleList")

SimpleList.init = function(self)
    self.listLength, self.updateListLength = Roact.createBinding(0)
end

SimpleList.render = function(self)
    local theme = self.props.theme
    local sections = self.props.sections

    local listItems = {}

    for i = 1, #sections do
        local section = sections[i]
        local shouldShowSectionHeader = true

        if ((not self.props.showAllSections) and (((i == 1) and (#sections == 1)) or (#section.items < 1))) then
            shouldShowSectionHeader = false
        end

        if (shouldShowSectionHeader) then
            table.insert(listItems, Roact.createElement(StandardTextLabel, {
                Size = UDim2.new(1, 0, 0, self.props.itemHeight),
                LayoutOrder = #listItems + 1,
                BackgroundTransparency = 0,

                Text = section.name,
                Font = Enum.Font.SourceSansBold,
                TextSize = self.props.TextSize,

                BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.HeaderSection),
            }, {
                UIPadding = if self.props.itemPadding then
                    Roact.createElement(StandardUIPadding, {
                        paddings = {0, 0, self.props.itemPadding, 0}
                    })
                else nil,
            }))
        end

        for j = 1, #section.items do
            local item = section.items[j]
            local children

            if (item[Roact.Children]) then
                children = item[Roact.Children]
            else
                if (self.props.itemPadding) then
                    children = {
                        UIPadding = Roact.createElement(StandardUIPadding, {
                            paddings = {0, 0, self.props.itemPadding, 0}
                        })
                    }
                end
            end

            table.insert(listItems, Roact.createElement("TextButton", {
                Size = UDim2.new(1, 0, 0, self.props.itemHeight),
                AutoButtonColor = false,
                BackgroundTransparency = 0,
                BorderSizePixel = 0,
                LayoutOrder = #listItems + 1,

                Font = Enum.Font.SourceSans,
                TextSize = self.props.TextSize,
                TextXAlignment = Enum.TextXAlignment.Left,
                TextYAlignment = Enum.TextYAlignment.Center,
                Text = item.name,

                BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Button),
                TextColor3 = theme:GetColor(Enum.StudioStyleGuideColor.ButtonText),

                [Roact.Event.MouseEnter] = function(obj)
                    obj.BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Hover)
                end,

                [Roact.Event.MouseLeave] = function(obj)
                    obj.BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Button)
                end,

                [Roact.Event.MouseButton1Down] = function(obj)
                    obj.BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Button, Enum.StudioStyleGuideModifier.Pressed)
                end,

                [Roact.Event.MouseButton1Up] = function(obj)
                    obj.BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Button)
                end,

                [Roact.Event.Activated] = function()
                    item.onActivated()
                end,
            }, children))
        end
    end

    listItems.UIListLayout = Roact.createElement(StandardUIListLayout, {
        [Roact.Change.AbsoluteContentSize] = function(obj)
            self.updateListLength(obj.AbsoluteContentSize.Y)
        end,

        preset = 1,
    })

    return Roact.createElement(StandardScrollingFrame, {
        AnchorPoint = self.props.AnchorPoint,
        Position = self.props.Position,
        Size = self.props.Size,

        CanvasSize = self.listLength:map(function(listLength)
            return UDim2.new(0, 0, 0, listLength)
        end),
    }, listItems)
end

return ConnectTheme(SimpleList)


-- A customisable numeric slider

local root = script.Parent.Parent
local Common = root.Common

local CommonModules = Common.Modules
local Style = require(CommonModules.Style)

local CommonIncludes = Common.Includes
local Roact = require(CommonIncludes.RoactRodux.Roact)

local CommonComponents = Common.Components
local Button = require(CommonComponents.Button)
local ConnectTheme = require(CommonComponents.ConnectTheme)
local TextInput = require(CommonComponents.TextInput)

local StandardComponents = CommonComponents.StandardComponents
local StandardTextLabel = require(StandardComponents.TextLabel)
local StandardUICorner = require(StandardComponents.UICorner)

local Modules = root.Modules
local ColorEditorInputSignals = require(Modules.EditorInputSignals).ColorEditor

---

--[[
    props
        AnchorPoint?
        Position?
        LayoutOrder?

        sliderLabel: string
        unitLabel: string?
        value: number

        markerColor: Color3?
        sliderGradient: ColorSequence

        keypoints: array<{
            value: number,
            color: Color3
        }>?

        valueToText: (number) -> string
        textToValue: (string) -> number?
        valueChanged: (number) -> nil

    store props
        theme: StudioTheme
]]

local Slider = Roact.PureComponent:extend("Slider")

Slider.init = function(self)
    self.center, self.updateCenter = Roact.createBinding(Vector2.new(0, 0))
    self.size, self.updateSize = Roact.createBinding(0)

    self.updateValue = function(cursorPosition)
        local distanceFromCenter = cursorPosition - self.center:getValue()
        local value = math.clamp((distanceFromCenter.X / self.size:getValue()) + 0.5, 0, 1)

        self.props.valueChanged(value)
    end

    self:setState({
        tracking = false,
    })
end

Slider.didMount = function(self)
    self.mousePositionChanged = ColorEditorInputSignals.MousePositionChanged.Event:subscribe(function(cursorPosition: Vector2)
        if (not self.state.tracking) then return end

        self.updateValue(cursorPosition)
    end)
end

Slider.willUnmount = function(self)
    self.mousePositionChanged:unsubscribe()
end

Slider.render = function(self)
    local theme = self.props.theme
    local value = self.props.value

    local keypointsFrame

    if (self.props.keypoints) then
        local keypointComponents = {}

        for i = 1, #self.props.keypoints do
            local keypoint = self.props.keypoints[i]

            keypointComponents[i] = Roact.createElement(Button, {
                AnchorPoint = Vector2.new(0.5, 0.5),
                Position = UDim2.new(keypoint.value, 0, 0.5, 0),
                LayoutOrder = i,

                displayType = "color",
                color = keypoint.color,

                onActivated = function()
                    self.props.valueChanged(keypoint.value)
                end
            })
        end

        keypointsFrame = Roact.createElement("Frame", {
            AnchorPoint = Vector2.new(0, 1),
            Position = UDim2.new(0, Style.Constants.StandardButtonHeight / 2, 1, 0),
            Size = UDim2.new(1, -(60 + Style.Constants.StandardButtonHeight + Style.Constants.MinorElementPadding), 0, Style.Constants.StandardButtonHeight),
            BackgroundTransparency = 1,
            BorderSizePixel = 0,
        }, keypointComponents)
    end

    return Roact.createElement("Frame", {
        AnchorPoint = self.props.AnchorPoint,
        Position = self.props.Position,
        Size = if self.props.keypoints then UDim2.new(1, 0, 0, 66) else UDim2.new(1, 0, 0, 40),
        LayoutOrder = self.props.LayoutOrder,
        BackgroundTransparency = 1,
        BorderSizePixel = 0
    }, {
        NameLabel = Roact.createElement(StandardTextLabel, {
            AnchorPoint = Vector2.new(0.5, 0),
            Position = UDim2.new(0.5, 0, 0, 0),
            Size = UDim2.new(1, 0, 0, Style.Constants.StandardTextSize),
            Text = self.props.sliderLabel,
        }),

        SliderBorder = Roact.createElement("Frame", {
            AnchorPoint = Vector2.new(0, 0),
            Position = UDim2.new(0, 0, 0, Style.Constants.StandardTextSize + Style.Constants.MinorElementPadding),
            Size = UDim2.new(1, -(60 + Style.Constants.MinorElementPadding), 0, Style.Constants.StandardInputHeight),
            BackgroundTransparency = 0,
            BorderSizePixel = 0,

            BackgroundColor3 = theme:GetColor(
                Enum.StudioStyleGuideColor.InputFieldBorder,
                if self.state.tracking then Enum.StudioStyleGuideModifier.Selected else nil
            ),
        }, {
            UICorner = Roact.createElement(StandardUICorner),

            Slider = Roact.createElement("Frame", {
                AnchorPoint = Vector2.new(0.5, 0.5),
                Position = UDim2.new(0.5, 0, 0.5, 0),
                Size = UDim2.new(1, -2, 1, -2),
                BackgroundTransparency = 0,
                BorderSizePixel = 1,
                ClipsDescendants = true,

                BackgroundColor3 = Color3.new(1, 1, 1),
                BorderColor3 = theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBorder),

                [Roact.Event.InputBegan] = function(_, input)
                    if (self.state.inputFocused) then return end
                    if (input.UserInputType ~= Enum.UserInputType.MouseButton1) then return end

                    local inputPosition = input.Position

                    self:setState({
                        tracking = true,
                    })

                    self.updateValue(Vector2.new(inputPosition.X, inputPosition.Y))
                end,

                [Roact.Event.InputEnded] = function(_, input)
                    if (input.UserInputType ~= Enum.UserInputType.MouseButton1) then return end
                    if (not self.state.tracking) then return end

                    self:setState({
                        tracking = false,
                    })
                end,

                [Roact.Change.AbsolutePosition] = function(obj)
                    local sliderPosition = obj.AbsolutePosition
                    local sliderSize = obj.AbsoluteSize

                    self.updateCenter(sliderPosition + (sliderSize / 2))
                    self.updateSize(sliderSize.X)
                end,

                [Roact.Change.AbsoluteSize] = function(obj)
                    local sliderPosition = obj.AbsolutePosition
                    local sliderSize = obj.AbsoluteSize

                    self.updateCenter(sliderPosition + (sliderSize / 2))
                    self.updateSize(sliderSize.X)
                end,
            }, {
                UICorner = Roact.createElement(StandardUICorner),

                UIGradient = Roact.createElement("UIGradient", {
                    Color = self.props.sliderGradient
                }),

                Marker = Roact.createElement("Frame", {
                    AnchorPoint = Vector2.new(0.5, 0.5),
                    Position = UDim2.new(value, 0, 0.5, 0),
                    Size = Style.UDim2.MarkerSize,
                    BackgroundTransparency = 0,
                    BorderSizePixel = 1,

                    BackgroundColor3 = self.props.markerColor or theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)
                }, {
                    UICorner = Roact.createElement(StandardUICorner, { circular = true }),
                }),
            }),
        }),

        Keypoints = keypointsFrame,

        InputFrame = Roact.createElement("Frame", {
            AnchorPoint = Vector2.new(1, 0),
            Position = UDim2.new(1, 0, 0, 18),
            Size = UDim2.new(0, 60, 0, Style.Constants.StandardInputHeight),
            BackgroundTransparency = 1,
            BorderSizePixel = 0
        }, {
            Input = Roact.createElement(TextInput, {
                AnchorPoint = Vector2.new(0, 0.5),
                Position = UDim2.new(0, 0, 0.5, 0),
                Size = UDim2.new(1, if self.props.unitLabel then -(10 + Style.Constants.MinorElementPadding) else 0, 1, 0),
                TextXAlignment = Enum.TextXAlignment.Center,
                Text = self.props.valueToText(value),

                selectTextOnFocus = true,

                isTextAValidValue = function(text)
                    return (if self.props.textToValue(text) then true else false)
                end,

                onSubmit = function(newText)
                    local newValue = self.props.textToValue(newText)

                    if (type(newValue) ~= "number") then return end
                    if ((newValue < 0) or (newValue > 1)) then return end

                    self.props.valueChanged(newValue)
                end
            }),

            UnitLabel = if self.props.unitLabel then
                Roact.createElement(StandardTextLabel, {
                    AnchorPoint = Vector2.new(1, 0.5),
                    Position = UDim2.new(1, 0, 0.5, 0),
                    Size = UDim2.new(0, 10, 1, 0),
                    Text = self.props.unitLabel,
                })
            else nil
        })
    })
end

---

return ConnectTheme(Slider)


-- A generic color slider page

local root = script.Parent.Parent
local Common = root.Common

local CommonModules = Common.Modules
local Style = require(CommonModules.Style)

local CommonIncludes = Common.Includes
local Color = require(CommonIncludes.Color).Color
local Roact = require(CommonIncludes.RoactRodux.Roact)
local RoactRodux = require(CommonIncludes.RoactRodux.RoactRodux)

local CommonComponents = Common.Components
local StandardUIListLayout = require(CommonComponents.StandardComponents.UIListLayout)

local Modules = root.Modules
local Enums = require(Modules.Enums)
local Util = require(Modules.Util)

local Components = root.Components
local Slider = require(Components.Slider)

---

--[[
    props
        colorSpace: string
        editorKey: string

        componentKeys: array<string>
        componentRanges: dictionary<string, {number, number}>
        componentDisplayRanges: dictionary<string, {number, number}>?
        componentLabels: dictionary<string, string>
        componentUnitLabels: dictionary<string, string>

        componentSliderGradientGenerators: dictionary<string, (dictionary<string, number>) -> ColorSequence>
        sliderMarkerColorGenerators: dictionary<string, (dictionary<string, number>, StudioTheme) -> Color3>?

    store props
        theme: StudioTheme
        color: Color
        editor: string

        setColor: (Color, string) -> nil
]]

local SliderPage = Roact.PureComponent:extend("SliderPage")

SliderPage.init = function(self, initProps)
    local componentKeys = initProps.componentKeys
    local components = { initProps.color:to(initProps.colorSpace) }
    local componentDictionary = {}

    for i = 1, #componentKeys do
        componentDictionary[componentKeys[i]] = components[i]
    end

    self.componentValueToNormal = function(component, value)
        local componentRanges = self.props.componentRanges[component]
        local min, max = componentRanges[1], componentRanges[2]

        return Util.inverseLerp(min, max, value)
    end

    self.componentNormalToValue = function(component, normal)
        local componentRanges = self.props.componentRanges[component]
        local min, max = componentRanges[1], componentRanges[2]

        return Util.lerp(min, max, normal)
    end

    self.componentNormalToDisplay = function(component, normal)
        local componentDisplayRanges = self.props.componentDisplayRanges

        if (not (componentDisplayRanges and componentDisplayRanges[component])) then
            componentDisplayRanges = self.props.componentRanges
        end

        local displayRange = componentDisplayRanges[component]
        local min, max = displayRange[1], displayRange[2]

        return Util.lerp(min, max, normal)
    end

    self.componentNormalToTextFactory = function(component)
        return function(normal)
            return tostring(math.floor(self.componentNormalToDisplay(component, normal)))
        end
    end

    self.componentTextToNormalFactory = function(component)
        return function(text)
            local value = tonumber(text)
            if (not value) then return end

            local normal = self.componentValueToNormal(component, value)
            return math.clamp(normal, 0, 1)
        end
    end

    self:setState({
        components = componentDictionary
    })
end

SliderPage.getDerivedStateFromProps = function(props, state)
    if (props.editor == props.editorKey) then return end

    if (state.captureFocus) then
        return {
            captureFocus = Roact.None,
        }
    end

    local components = { props.color:to(props.colorSpace) }

    local changed = false
    local newComponentsDictionary = {}

    for i = 1, #components do
        local newComponent = components[i]
        local componentKey = props.componentKeys[i]

        if (componentKey == "H") then
            newComponentsDictionary[componentKey] = if (newComponent ~= newComponent) then 0 else newComponent
        else
            newComponentsDictionary[componentKey] = newComponent
        end

        if (newComponent ~= state[componentKey]) then
            changed = true
        end
    end

    return changed and {
        components = newComponentsDictionary
    } or nil
end

SliderPage.didUpdate = function(self, prevProps)
    local colorSpace = self.props.colorSpace
    if (colorSpace == prevProps.colorSpace) then return end

    local componentKeys = self.props.componentKeys
    local components = { self.props.color:to(colorSpace) }
    local componentDictionary = {}

    for i = 1, #componentKeys do
        componentDictionary[componentKeys[i]] = components[i]
    end

    self:setState({
        components = componentDictionary
    })
end

SliderPage.render = function(self)
    local theme = self.props.theme
    local colorSpace = self.props.colorSpace
    local editorKey = self.props.editorKey
    local editor = self.props.editor

    local componentKeys = self.props.componentKeys
    local componentLabels = self.props.componentLabels
    local componentUnitLabels = self.props.componentUnitLabels

    local componentSliderGradientGenerators = self.props.componentSliderGradientGenerators
    local sliderMarkerColorGenerators = self.props.sliderMarkerColorGenerators

    local components = self.state.components
    local elements = {}
    local componentsArray = {}

    -- check that the components match
    for i = 1, #componentKeys do
        if (not components[componentKeys[i]]) then
            return
        end
    end

    for i = 1, #componentKeys do
        componentsArray[i] = components[componentKeys[i]]
    end

    for i = 1, #componentKeys do
        local componentKey = componentKeys[i]
        local sliderMarkerColorGenerator = if sliderMarkerColorGenerators then sliderMarkerColorGenerators[componentKey] else nil

        elements[componentKey] = Roact.createElement(Slider, {
            LayoutOrder = i,

            sliderLabel = componentLabels[componentKey],
            unitLabel = componentUnitLabels[componentKey],

            value = self.componentValueToNormal(componentKey, components[componentKey]),
            valueToText = self.componentNormalToTextFactory(componentKey),
            textToValue = self.componentTextToNormalFactory(componentKey),

            sliderGradient = componentSliderGradientGenerators[componentKey](components),

            markerColor = if sliderMarkerColorGenerator then sliderMarkerColorGenerator(components, theme) else
                Color.from(colorSpace, table.unpack(componentsArray)):bestContrastingColor(
                    Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)),
                    Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)):invert()
                ):toColor3(),

            valueChanged = function(normal)
                local newComponent = self.componentNormalToValue(componentKey, normal)
                local newComponents = {}
                local newComponentsArray = {}

                for j = 1, #componentKeys do
                    local newComponentKey = componentKeys[j]

                    if (newComponentKey == componentKey) then
                        newComponents[newComponentKey] = newComponent
                        newComponentsArray[j] = newComponent
                    else
                        newComponents[newComponentKey] = components[newComponentKey]
                        newComponentsArray[j] = components[newComponentKey]
                    end
                end

                self:setState({
                    captureFocus = if (editor ~= editorKey) then true else nil,
                    components = newComponents,
                })

                self.props.setColor(Color.from(colorSpace, table.unpack(newComponentsArray)), editorKey)
            end
        })
    end

    elements.UIListLayout = Roact.createElement(StandardUIListLayout, {
        Padding = UDim.new(0, Style.Constants.MajorElementPadding),

        preset = 1,
    })

    return Roact.createFragment(elements)
end

---

return RoactRodux.connect(function(state)
    return {
        theme = state.theme,
        color = state.colorEditor.color,
        editor = state.colorEditor.authoritativeEditor,
    }
end, function(dispatch)
    return {
        setColor = function(newColor, editor)
            dispatch({
                type = Enums.StoreActionType.ColorEditor_SetColor,
                color = newColor,
                editor = editor
            })
        end
    }
end)(SliderPage)


-- A Pages container for various color sliders in various color spaces

local root = script.Parent.Parent
local Common = root.Common

local CommonModules = Common.Modules
local Translator = require(CommonModules.Translator)

local CommonIncludes = Common.Includes
local Color = require(CommonIncludes.Color).Color
local Roact = require(CommonIncludes.RoactRodux.Roact)
local RoactRodux = require(CommonIncludes.RoactRodux.RoactRodux)

local Modules = root.Modules
local Enums = require(Modules.Enums)

local Components = root.Components
local GreyscaleSliderPage = require(Components.GreyscaleSliderPage)
local Pages = require(Components.Pages)
local SliderPage = require(Components.SliderPage)
local TemperatureSliderPage = require(Components.TemperatureSliderPage)

---

local uiTranslations = Translator.GenerateTranslationTable({
    "RGB_ColorType",
    "CMYK_ColorType",
    "HSB_ColorType",
    "HSL_ColorType",

    "Hue_Component",
    "Saturation_Component",

    "HSB_Brightness_Component",
    "HSL_Lightness_Component",

    "RGB_Red_Component",
    "RGB_Green_Component",
    "RGB_Blue_Component",

    "CMYK_Cyan_Component",
    "CMYK_Magenta_Component",
    "CMYK_Yellow_Component",
    "CMYK_Key_Component",

    "Monochrome_Slider",
    "Temperature_Label",
})

---

--[[
    store props

        lastSliderPage: number
        updateSliderPage: (number) -> nil
]]

local SliderPages = Roact.PureComponent:extend("SliderPages")

SliderPages.render = function(self)
    return Roact.createElement(Pages, {
        selectedPage = self.props.lastSliderPage,
        onPageChanged = self.props.updateSliderPage,

        pageSections = {
            {
                name = "",

                items = {
                    {
                        name = uiTranslations["RGB_ColorType"],

                        content = Roact.createElement(SliderPage, {
                            colorSpace = "RGB",
                            editorKey = Enums.EditorKey.RGBSlider,
                            componentKeys = {"R", "G", "B"},

                            componentRanges = {
                                R = {0, 255},
                                G = {0, 255},
                                B = {0, 255}
                            },

                            componentLabels = {
                                R = uiTranslations["RGB_Red_Component"],
                                G = uiTranslations["RGB_Green_Component"],
                                B = uiTranslations["RGB_Blue_Component"]
                            },

                            componentUnitLabels = {
                                R = nil,
                                G = nil,
                                B = nil
                            },

                            componentSliderGradientGenerators = {
                                R = function(components)
                                    return ColorSequence.new(Color3.fromRGB(0, components.G, components.B), Color3.fromRGB(255, components.G, components.B))
                                end,

                                G = function(components)
                                    return ColorSequence.new(Color3.fromRGB(components.R, 0, components.B), Color3.fromRGB(components.R, 255, components.B))
                                end,

                                B = function(components)
                                    return ColorSequence.new(Color3.fromRGB(components.R, components.G, 0), Color3.fromRGB(components.R, components.G, 255))
                                end,
                            },
                        })
                    },

                    {
                        name = uiTranslations["CMYK_ColorType"],

                        content = Roact.createElement(SliderPage, {
                            colorSpace = "CMYK",
                            editorKey = Enums.EditorKey.CMYKSlider,
                            componentKeys = {"C", "M", "Y", "K"},

                            componentRanges = {
                                C = {0, 1},
                                M = {0, 1},
                                Y = {0, 1},
                                K = {0, 1}
                            },

                            componentDisplayRanges = {
                                C = {0, 100},
                                M = {0, 100},
                                Y = {0, 100},
                                K = {0, 100}
                            },

                            componentLabels = {
                                C = uiTranslations["CMYK_Cyan_Component"],
                                M = uiTranslations["CMYK_Magenta_Component"],
                                Y = uiTranslations["CMYK_Yellow_Component"],
                                K = uiTranslations["CMYK_Key_Component"],
                            },

                            componentUnitLabels = {
                                C = "%",
                                M = "%",
                                Y = "%",
                                K = "%",
                            },

                            componentSliderGradientGenerators = {
                                C = function(components)
                                    return ColorSequence.new(
                                        Color.fromCMYK(0, components.M, components.Y, components.K):toColor3(),
                                        Color.fromCMYK(1, components.M, components.Y, components.K):toColor3()
                                    )
                                end,

                                M = function(components)
                                    return ColorSequence.new(
                                        Color.fromCMYK(components.C, 0, components.Y, components.K):toColor3(),
                                        Color.fromCMYK(components.C, 1, components.Y, components.K):toColor3()
                                    )
                                end,

                                Y = function(components)
                                    return ColorSequence.new(
                                        Color.fromCMYK(components.C, components.M, 0, components.K):toColor3(),
                                        Color.fromCMYK(components.C, components.M, 1, components.K):toColor3()
                                    )
                                end,

                                K = function(components)
                                    return ColorSequence.new(
                                        Color.fromCMYK(components.C, components.M, components.Y, 0):toColor3(),
                                        Color.fromCMYK(components.C, components.M, components.Y, 1):toColor3()
                                    )
                                end,
                            },
                        })
                    },

                    {
                        name = uiTranslations["HSB_ColorType"],

                        content = Roact.createElement(SliderPage, {
                            colorSpace = "HSB",
                            editorKey = Enums.EditorKey.HSBSlider,
                            componentKeys = {"H", "S", "B"},

                            componentRanges = {
                                H = {0, 360},
                                S = {0, 1},
                                B = {0, 1}
                            },

                            componentDisplayRanges = {
                                S = {0, 100},
                                B = {0, 100},
                            },

                            componentLabels = {
                                H = uiTranslations["Hue_Component"],
                                S = uiTranslations["Saturation_Component"],
                                B = uiTranslations["HSB_Brightness_Component"]
                            },

                            componentUnitLabels = {
                                H = "°",
                                S = "%",
                                B = "%"
                            },

                            componentSliderGradientGenerators = {
                                H = function()
                                    return ColorSequence.new({
                                        ColorSequenceKeypoint.new(0, Color3.fromHSV(0, 1, 1)),
                                        ColorSequenceKeypoint.new(1/6, Color3.fromHSV(1/6, 1, 1)),
                                        ColorSequenceKeypoint.new(2/6, Color3.fromHSV(2/6, 1, 1)),
                                        ColorSequenceKeypoint.new(3/6, Color3.fromHSV(3/6, 1, 1)),
                                        ColorSequenceKeypoint.new(4/6, Color3.fromHSV(4/6, 1, 1)),
                                        ColorSequenceKeypoint.new(5/6, Color3.fromHSV(5/6, 1, 1)),
                                        ColorSequenceKeypoint.new(1, Color3.fromHSV(1, 1, 1))
                                    })
                                end,

                                S = function(components)
                                    return ColorSequence.new(
                                        Color3.fromHSV(components.H / 360, 0, components.B),
                                        Color3.fromHSV(components.H / 360, 1, components.B)
                                    )
                                end,

                                B = function(components)
                                    return ColorSequence.new(
                                        Color3.fromHSV(components.H / 360, components.S, 0),
                                        Color3.fromHSV(components.H / 360, components.S, 1)
                                    )
                                end,
                            },

                            sliderMarkerColorGenerators = {
                                H = function(components, theme)
                                    return Color.fromHSB(components.H / 360, 1, 1):bestContrastingColor(
                                        Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)),
                                        Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)):invert()
                                    ):toColor3()
                                end
                            }
                        })
                    },

                    {
                        name = uiTranslations["HSL_ColorType"],

                        content = Roact.createElement(SliderPage, {
                            colorSpace = "HSL",
                            editorKey = Enums.EditorKey.HSLSlider,
                            componentKeys = {"H", "S", "L"},

                            componentRanges = {
                                H = {0, 360},
                                S = {0, 1},
                                L = {0, 1}
                            },

                            componentDisplayRanges = {
                                S = {0, 100},
                                L = {0, 100},
                            },

                            componentLabels = {
                                H = uiTranslations["Hue_Component"],
                                S = uiTranslations["Saturation_Component"],
                                L = uiTranslations["HSL_Lightness_Component"]
                            },

                            componentUnitLabels = {
                                H = "°",
                                S = "%",
                                L = "%"
                            },

                            componentSliderGradientGenerators = {
                                H = function()
                                    return ColorSequence.new({
                                        ColorSequenceKeypoint.new(0, Color3.fromHSV(0, 1, 1)),
                                        ColorSequenceKeypoint.new(1/6, Color3.fromHSV(1/6, 1, 1)),
                                        ColorSequenceKeypoint.new(2/6, Color3.fromHSV(2/6, 1, 1)),
                                        ColorSequenceKeypoint.new(3/6, Color3.fromHSV(3/6, 1, 1)),
                                        ColorSequenceKeypoint.new(4/6, Color3.fromHSV(4/6, 1, 1)),
                                        ColorSequenceKeypoint.new(5/6, Color3.fromHSV(5/6, 1, 1)),
                                        ColorSequenceKeypoint.new(1, Color3.fromHSV(1, 1, 1))
                                    })
                                end,

                                S = function(components)
                                    return ColorSequence.new({
                                        ColorSequenceKeypoint.new(0, Color.fromHSL(components.H, 0, components.L):toColor3()),
                                        ColorSequenceKeypoint.new(0.5, Color.fromHSL(components.H, 0.5, components.L):toColor3()),
                                        ColorSequenceKeypoint.new(1, Color.fromHSL(components.H, 1, components.L):toColor3())
                                    })
                                end,

                                L = function(components)
                                    return ColorSequence.new({
                                        ColorSequenceKeypoint.new(0, Color.fromHSL(components.H, components.S, 0):toColor3()),
                                        ColorSequenceKeypoint.new(0.5, Color.fromHSL(components.H, components.S, 0.5):toColor3()),
                                        ColorSequenceKeypoint.new(1, Color.fromHSL(components.H, components.S, 1):toColor3())
                                    })
                                end,
                            },

                            sliderMarkerColorGenerators = {
                                H = function(components, theme)
                                    return Color.fromHSB(components.H / 360, 1, 1):bestContrastingColor(
                                        Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)),
                                        Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)):invert()
                                    ):toColor3()
                                end
                            }
                        })
                    },

                    {
                        name = uiTranslations["Monochrome_Slider"],
                        content = Roact.createElement(GreyscaleSliderPage)
                    },

                    {
                        name = uiTranslations["Temperature_Label"],
                        content = Roact.createElement(TemperatureSliderPage)
                    },
                }
            }
        }
    })
end

return RoactRodux.connect(function(state)
    return {
        lastSliderPage = state.sessionData.lastSliderPage,
    }
end, function(dispatch)
    return {
        updateSliderPage = function(section, page)
            dispatch({
                type = Enums.StoreActionType.UpdateSessionData,
                slice = {
                    lastSliderPage = {section, page}
                }
            })
        end,
    }
end)(SliderPages)


-- A color slider page for black-body color temperatures

local root = script.Parent.Parent
local Common = root.Common

local CommonModules = Common.Modules
local Constants = require(CommonModules.Constants)
local Style = require(CommonModules.Style)
local Translator = require(CommonModules.Translator)

local CommonIncludes = Common.Includes
local Color = require(CommonIncludes.Color).Color
local Roact = require(CommonIncludes.RoactRodux.Roact)
local RoactRodux = require(CommonIncludes.RoactRodux.RoactRodux)

local StandardComponents = Common.Components.StandardComponents
local StandardTextLabel = require(StandardComponents.TextLabel)
local StandardUIPadding = require(StandardComponents.UIPadding)

local Modules = root.Modules
local Enums = require(Modules.Enums)
local Util = require(Modules.Util)

local Components = root.Components
local SimpleList = require(Components.SimpleList)
local Slider = require(Components.Slider)

---

local EDITOR_KEY = Enums.EditorKey.KelvinSlider

local valueToText = function(value)
    return math.floor(Util.lerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, value))
end

local textToValue = function(text)
    local n = tonumber(text)
    if (not n) then return end
    if ((n < Constants.KELVIN_LOWER_RANGE) or (n > Constants.KELVIN_UPPER_RANGE)) then return end

    return Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, n)
end

local temperatureGradient = ColorSequence.new({
    ColorSequenceKeypoint.new(0, Color.fromTemperature(1000):toColor3()),
    ColorSequenceKeypoint.new(Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 2000), Color.fromTemperature(2000):toColor3()),
    ColorSequenceKeypoint.new(Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 6000), Color.fromTemperature(6000):toColor3()),
    ColorSequenceKeypoint.new(Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 6500), Color.fromTemperature(6500):toColor3()),
    ColorSequenceKeypoint.new(Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 7000), Color.fromTemperature(7000):toColor3()),
    ColorSequenceKeypoint.new(1, Color.fromTemperature(10000):toColor3()),
})

local uiTranslations = Translator.GenerateTranslationTable({
    "Temperature_Label",
    "Presets_Label",
})

local temperaturePresets = {
    { name = Translator.FormatByKey("MatchFlame_Preset"), temperature = 1700 },
    { name = Translator.FormatByKey("Candlelight_Preset"), temperature = 1850 },
    { name = Translator.FormatByKey("Incandescent_Preset"), temperature = 2400 },
    { name = Translator.FormatByKey("SoftWhiteIncandescent_Preset"), temperature = 2550 },
    { name = Translator.FormatByKey("SoftWhite_Preset"), temperature = 2700 },
    { name = Translator.FormatByKey("WarmWhite_Preset"), temperature = 3000 },
    { name = Translator.FormatByKey("HorizonDaylight_Preset"), temperature = 5000 },
    { name = Translator.FormatByKey("Daylight_Preset"), temperature = 6500 },
    { name = Translator.FormatByKey("ClearSky_Preset"), temperature = 10000 },
}

---

--[[
    store props
        theme: StudioTheme
        color: Color
        editor: string

        setColor: (Color) -> nil
]]

local TemperatureSliderPage = Roact.PureComponent:extend("TemperatureSliderPage")

TemperatureSliderPage.init = function(self, initProps)
    self:setState({
        temperature = initProps.color:toTemperature()
    })
end

TemperatureSliderPage.getDerivedStateFromProps = function(props, state)
    if (props.editor == EDITOR_KEY) then return end

    if (state.captureFocus) then
        return {
            captureFocus = Roact.None,
        }
    end

    local temperature = props.color:toTemperature()
    if (temperature == state.temperature) then return end

    return {
        temperature = temperature
    }
end

TemperatureSliderPage.render = function(self)
    local theme = self.props.theme
    local editor = self.props.editor
    local temperature = self.state.temperature

    local presetItems = {}

    for i = 1, #temperaturePresets do
        local preset = temperaturePresets[i]
        local presetTemperature = preset.temperature

        presetItems[i] = {
            name = preset.name,

            onActivated = function()
                self:setState({
                    captureFocus = if (editor ~= EDITOR_KEY) then true else nil,
                    temperature = presetTemperature
                })

                self.props.setColor(Color.fromTemperature(presetTemperature))
            end,

            [Roact.Children] = {
                UIPadding = Roact.createElement(StandardUIPadding, {
                    paddings = {0, Style.Constants.SpaciousElementPadding}
                }),

                KelvinLabel = Roact.createElement(StandardTextLabel, {
                    AnchorPoint = Vector2.new(1, 0.5),
                    Position = UDim2.new(1, 0, 0.5, 0),
                    Size = UDim2.new(1, 0, 1, 0),

                    Text = preset.temperature,
                    TextXAlignment = Enum.TextXAlignment.Right,
                    TextYAlignment = Enum.TextYAlignment.Center,
                })
            }
        }
    end

    return Roact.createFragment({
        Slider = Roact.createElement(Slider, {
            value = Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, temperature),
            layoutOrder = 0,

            sliderLabel = uiTranslations["Temperature_Label"],
            sliderGradient = temperatureGradient,
            unitLabel = "K",

            markerColor = Color.fromTemperature(temperature):bestContrastingColor(
                Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)),
                Color.fromColor3(theme:GetColor(Enum.StudioStyleGuideColor.ColorPickerFrame)):invert()
            ):toColor3(),

            valueToText = valueToText,
            textToValue = textToValue,

            isTextAValidValue = function(text)
                return (if textToValue(text) then true else false)
            end,

            valueChanged = function(value)
                local newTemperature = Util.lerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, value)

                self:setState({
                    captureFocus = if (editor ~= EDITOR_KEY) then true else nil,
                    temperature = newTemperature
                })

                self.props.setColor(Color.fromTemperature(newTemperature))
            end
        }),

        ScaleLowerLimitLabel = Roact.createElement(StandardTextLabel, {
            AnchorPoint = Vector2.new(0, 0),
            Position = UDim2.new(0, 0, 0, 40),
            Size = UDim2.new(0, 100, 0, Style.Constants.StandardTextSize),
            Text = Constants.KELVIN_LOWER_RANGE,
        }),

        ScaleUpperLimitLabel = Roact.createElement(StandardTextLabel, {
            AnchorPoint = Vector2.new(1, 0),
            Position = UDim2.new(1, -(60 + Style.Constants.MinorElementPadding), 0, 40),
            Size = UDim2.new(0, 100, 0, Style.Constants.StandardTextSize),

            Text = Constants.KELVIN_UPPER_RANGE,
            TextXAlignment = Enum.TextXAlignment.Right,
            TextYAlignment = Enum.TextYAlignment.Center,
        }),

        PresetsContainer = Roact.createElement("Frame", {
            AnchorPoint = Vector2.new(0.5, 1),
            Position = UDim2.new(0.5, 0, 1, 0),
            Size = UDim2.new(1, 0, 1, -58),
            BackgroundTransparency = 1,
            BorderSizePixel = 0,
        }, {
            PresetsLabel = Roact.createElement(StandardTextLabel, {
                AnchorPoint = Vector2.new(0.5, 0),
                Position = UDim2.new(0.5, 0, 0, 0),
                Size = UDim2.new(1, 0, 0, Style.Constants.StandardTextSize),
                Text = uiTranslations["Presets_Label"],
            }),

            PresetsList = Roact.createElement(SimpleList, {
                AnchorPoint = Vector2.new(0.5, 1),
                Position = UDim2.new(0.5, 0, 1, 0),
                Size = UDim2.new(1, -2, 1, -18),
                TextSize = Style.Constants.StandardTextSize,

                itemHeight = Style.Constants.StandardButtonHeight,

                sections = {
                    {
                        name = "",
                        items = presetItems
                    }
                }
            }),
        }),
    })
end

return RoactRodux.connect(function(state)
    return {
        theme = state.theme,
        color = state.colorEditor.color,
        editor = state.colorEditor.authoritativeEditor,
    }
end, function(dispatch)
    return {
        setColor = function(newColor)
            dispatch({
                type = Enums.StoreActionType.ColorEditor_SetColor,
                color = newColor,
                editor = EDITOR_KEY
            })
        end
    }
end)(TemperatureSliderPage)


--!strict
-- Run-time validators for API call parameters

local root = script.Parent.Parent

local CommonIncludes = root.Common.Includes
local ColorLib = require(CommonIncludes.Color)
local t = require(CommonIncludes.t)

local Color = ColorLib.Color
local Gradient = ColorLib.Gradient

---

local APITypeValidators = {}

--[[
    Checks that a ColorPromptInfoArgument is valid.
    Note that this cannot check if ColorType and the
    argument type to OnColorChanged match.

    @param value The value to check
    @return If the value is a ColorPromptInfoArgument
    @return An error message if the value is not a ColorPromptInfoArgument
]]
APITypeValidators.ColorPromptInfoArgument = t.optional(t.interface({
    PromptTitle = t.optional(t.string),
    InitialColor = t.optional(t.union(Color.isAColor, t.Color3)),
    ColorType = t.optional(t.literal("Color", "Color3")),
    OnColorChanged = t.optional(t.callback)
}))

--[[
    Checks that a ColorPromptInfoArgument is valid.
    Note that this doesn't check if the number of keypoints
    and the provided precision is a valid combination, and
    cannot check if GradientType and the argument type to
    OnGradientChanged match.

    @param value The value to check
    @return If the value is a GradientPromptInfoArgument
    @return An error message if the value is not a GradientPromptInfoArgument
]]
APITypeValidators.GradientPromptInfoArgument = t.optional(t.interface({
    PromptTitle = t.optional(t.string),
    InitialGradient = t.optional(t.union(Gradient.isAGradient, t.ColorSequence)),
    InitialColorSpace = t.optional(t.literal("CMYK", "HPLuv", "HSB", "HSL", "HSLuv", "HSV", "HWB", "LCh", "LChab", "LChuv", "Lab", "Luv", "Oklab", "RGB", "xyY", "XYZ")),
    InitialHueAdjustment = t.optional(t.literal("Shorter", "Longer", "Increasing", "Decreasing", "Raw", "Specified")),
    InitialPrecision = t.optional(t.integer),
    GradientType = t.optional(t.literal("Gradient", "ColorSequence")),
    OnGradientChanged = t.optional(t.callback)
}))

return APITypeValidators


-- A list of built-in gradients

local root = script.Parent.Parent
local Common = root.Common

local CommonIncludes = Common.Includes
local Color = require(CommonIncludes.Color).Color

local CommonModules = Common.Modules
local Constants = require(CommonModules.Constants)
local Translator = require(CommonModules.Translator)

local Modules = root.Modules
local Util = require(Modules.Util)

---

return {
    {
        name = Translator.FormatByKey("BlackToWhite_BuiltInGradientName"),

        keypoints = {
            { Time = 0, Color = Color.new(0, 0, 0) },
            { Time = 1, Color = Color.new(1, 1, 1) }
        }
    },

    {
        name = Translator.FormatByKey("Hue_BuiltInGradientName"),
        colorSpace = "HSB",

        keypoints = {
            { Time = 0, Color = Color.fromHSB(0, 1, 1) },
            { Time = 1/6, Color = Color.fromHSB(60, 1, 1) },
            { Time = 2/6, Color = Color.fromHSB(120, 1, 1) },
            { Time = 3/6, Color = Color.fromHSB(180, 1, 1) },
            { Time = 4/6, Color = Color.fromHSB(240, 1, 1) },
            { Time = 5/6, Color = Color.fromHSB(300, 1, 1) },
            { Time = 1, Color = Color.fromHSB(360, 1, 1) }
        }
    },

    {
        name = Translator.FormatByKey("Temperature_Label"),

        keypoints = {
            { Time = 0, Color = Color.fromTemperature(1000) },
            { Time = Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 2000), Color = Color.fromTemperature(2000) },
            { Time = Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 6000), Color = Color.fromTemperature(6000) },
            { Time = Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 6500), Color = Color.fromTemperature(6500) },
            { Time = Util.inverseLerp(Constants.KELVIN_LOWER_RANGE, Constants.KELVIN_UPPER_RANGE, 7000), Color = Color.fromTemperature(7000) },
            { Time = 1, Color = Color.fromTemperature(10000) },
        }
    }
}


-- A palette of the available brick colors

local brickColorsPalette = {
    name = "BrickColors",
    colors = {}
}

for i = 1, 1032 do
    local brickColor = BrickColor.new(i)

    -- BrickColors that don't exist default to #194
    if ((brickColor.Number ~= 194) or (i == 194)) then
        local color = brickColor.Color

        table.insert(brickColorsPalette.colors, {
            name = brickColor.Name,

            color = { color.R * 255, color.G * 255, color.B * 255 }
        })
    end
end

return brickColorsPalette


--[[
    A palette of the web colors

    Named colors from CSS Color Module Level 4
    https://www.w3.org/TR/2020/WD-css-color-4-20201112/#named-colors
--]]

return {
    name = "Web Colors",

    colors = {
        {
            keywords = {"aliceblue"},
            name = "Alice Blue",
            color = {240, 248, 255}
        },

        {
            keywords = {"antiquewhite"},
            name = "Antique White",
            color = {250, 235, 215}
        },

        {
            keywords = {"aqua", "cyan"},
            name = "Aqua / Cyan",
            color = {0, 255, 255}
        },

        {
            keywords = {"aquamarine"},
            name = "Aquamarine",
            color = {127, 255, 212}
        },

        {
            keywords = {"azure"},
            name = "Azure",
            color = {240, 255, 255}
        },

        {
            keywords = {"beige"},
            name = "Beige",
            color = {245, 245, 220}
        },

        {
            keywords = {"bisque"},
            name = "Bisque",
            color = {255, 228, 196}
        },

        {
            keywords = {"black"},
            name = "Black",
            color = {0, 0, 0}
        },

        {
            keywords = {"blanchedalmond"},
            name = "Blanched Almond",
            color = {255, 235, 205}
        },

        {
            keywords = {"blue"},
            name = "Blue",
            color = {0, 0, 255}
        },

        {
            keywords = {"blueviolet"},
            name = "Blue Violet",
            color = {138, 43, 226}
        },

        {
            keywords = {"brown"},
            name = "Brown",
            color = {165, 42, 42}
        },

        {
            keywords = {"burlywood"},
            name = "Burly Wood",
            color = {222, 184, 135}
        },

        {
            keywords = {"cadetblue"},
            name = "Cadet Blue",
            color = {95, 158, 160}
        },

        {
            keywords = {"chartreuse"},
            name = "Chartreuse",
            color = {127, 255, 0}
        },

        {
            keywords = {"chocolate"},
            name = "Chocolate",
            color = {210, 105, 30}
        },

        {
            keywords = {"coral"},
            name = "Coral",
            color = {255, 127, 80}
        },

        {
            keywords = {"cornflowerblue"},
            name = "Cornflower Blue",
            color = {100, 149, 237}
        },

        {
            keywords = {"cornsilk"},
            name = "Cornsilk",
            color = {255, 248, 220}
        },

        {
            keywords = {"darkblue"},
            name = "Dark Blue",
            color = {0, 0, 139}
        },

        {
            keywords = {"darkcyan"},
            name = "Dark Cyan",
            color = {0, 139, 139}
        },

        {
            keywords = {"darkgoldenrod"},
            name = "Dark Golden Rod",
            color = {184, 134, 11}
        },

        {
            keywords = {"darkgray", "darkgrey"},
            name = "Dark Gray",
            color = {169, 169, 169}
        },

        {
            keywords = {"darkgreen"},
            name = "Dark Green",
            color = {0, 100, 0}
        },

        {
            keywords = {"darkkhaki"},
            name = "Dark Khaki",
            color = {189, 183, 107}
        },

        {
            keywords = {"darkmagenta"},
            name = "Dark Magenta",
            color = {139, 0, 139}
        },

        {
            keywords = {"darkolivegreen"},
            name = "Dark Olive Green",
            color = {85, 107, 47}
        },

        {
            keywords = {"darkorange"},
            name = "Dark Orange",
            color = {255, 140, 0}
        },

        {
            keywords = {"darkorchid"},
            name = "Dark Orchid",
            color = {153, 50, 204}
        },

        {
            keywords = {"darkred"},
            name = "Dark Red",
            color = {139, 0, 0}
        },

        {
            keywords = {"darksalmon"},
            name = "Dark Salmon",
            color = {233, 150, 122}
        },

        {
            keywords = {"darkseagreen"},
            name = "Dark Sea Green",
            color = {143, 188, 143}
        },

        {
            keywords = {"darkslateblue"},
            name = "Dark Slate Blue",
            color = {72, 61, 139}
        },

        {
            keywords = {"darkslategray", "darkslategrey"},
            name = "Dark Slate Gray",
            color = {47, 79, 79}
        },

        {
            keywords = {"darkturquoise"},
            name = "Dark Turquoise",
            color = {0, 206, 109}
        },

        {
            keywords = {"darkviolet"},
            name = "Dark Violet",
            color = {148, 0, 211}
        },

        {
            keywords = {"deeppink"},
            name = "Deep Pink",
            color = {255, 20, 147}
        },

        {
            keywords = {"deepskyblue"},
            name = "Deep Sky Blue",
            color = {0, 191, 255}
        },

        {
            keywords = {"dimgray", "dimgrey"},
            name = "Dim Gray",
            color = {105, 105, 105}
        },

        {
            keywords = {"dodgerblue"},
            name = "Dodger Blue",
            color = {30, 144, 255}
        },

        {
            keywords = {"firebrick"},
            name = "Fire Brick",
            color = {178, 34, 34}
        },

        {
            keywords = {"floralwhite"},
            name = "Floral White",
            color = {255, 250, 240}
        },

        {
            keywords = {"forestgreen"},
            name = "Forest Green",
            color = {34, 139, 34}
        },

        {
            keywords = {"fuchsia", "magenta"},
            name = "Fuchsia",
            color = {255, 0, 255}
        },

        {
            keywords = {"gainsboro"},
            name = "Gainsboro",
            color = {220, 220, 220}
        },

        {
            keywords = {"ghostwhite"},
            name = "Ghost White",
            color = {248, 248, 255}
        },

        {
            keywords = {"gold"},
            name = "Gold",
            color = {255, 215, 0}
        },

        {
            keywords = {"goldenrod"},
            name = "Golden Rod",
            color = {218, 165, 32}
        },

        {
            keywords = {"gray", "grey"},
            name = "Gray",
            color = {128, 128, 128}
        },

        {
            keywords = {"green"},
            name = "Green",
            color = {0, 128, 0}
        },

        {
            keywords = {"greenyellow"},
            name = "Green Yellow",
            color = {173, 255, 47}
        },

        {
            keywords = {"honeydew"},
            name = "Honeydew",
            color = {240, 255, 240}
        },

        {
            keywords = {"hotpink"},
            name = "Hot Pink",
            color = {255, 105, 180}
        },

        {
            keywords = {"indianred"},
            name = "Indian Red",
            color = {205, 92, 92}
        },

        {
            keywords = {"indigo"},
            name = "Indigo",
            color = {75, 0, 130}
        },

        {
            keywords = {"ivory"},
            name = "Ivory",
            color = {255, 255, 240}
        },

        {
            keywords = {"khaki"},
            name = "Khaki",
            color = {240, 230, 140}
        },

        {
            keywords = {"lavender"},
            name = "Lavender",
            color = {230, 230, 250}
        },

        {
            keywords = {"lavenderblush"},
            name = "Lavender Blush",
            color = {255, 240, 245}
        },

        {
            keywords = {"lawngreen"},
            name = "Lawn Green",
            color = {124, 252, 0}
        },

        {
            keywords = {"lemonchiffon"},
            name = "Lemon Chiffon",
            color = {255, 250, 205}
        },

        {
            keywords = {"lightblue"},
            name = "Light Blue",
            color = {173, 216, 230}
        },

        {
            keywords = {"lightcoral"},
            name = "Light Coral",
            color = {240, 128, 128}
        },

        {
            keywords = {"lightcyan"},
            name = "Light Cyan",
            color = {224, 255, 255}
        },

        {
            keywords = {"lightgoldenrodyellow"},
            name = "Light Golden Rod Yellow",
            color = {250, 250, 210}
        },

        {
            keywords = {"lightgray", "lightgrey"},
            name = "Light Gray",
            color = {211, 211, 211}
        },

        {
            keywords = {"lightgreen"},
            name = "Light Green",
            color = {144, 238, 144}
        },

        {
            keywords = {"lightpink"},
            name = "Light Pink",
            color = {255, 182, 193}
        },

        {
            keywords = {"lightsalmon"},
            name = "Light Salmon",
            color = {255, 160, 122}
        },

        {
            keywords = {"lightseagreen"},
            name = "Light Sea Green",
            color = {32, 178, 170}
        },

        {
            keywords = {"lightskyblue"},
            name = "Light Sky Blue",
            color = {135, 206, 250}
        },

        {
            keywords = {"lightslategray", "lightslategrey"},
            name = "Light Slate Gray",
            color = {119, 136, 153}
        },

        {
            keywords = {"lightsteelblue"},
            name = "Light Steel Blue",
            color = {176, 196, 222}
        },

        {
            keywords = {"lightyellow"},
            name = "Light Yellow",
            color = {255, 255, 224}
        },

        {
            keywords = {"lime"},
            name = "Lime",
            color = {0, 255, 0}
        },

        {
            keywords = {"limegreen"},
            name = "Lime Green",
            color = {50, 205, 50}
        },

        {
            keywords = {"linen"},
            name = "Linen",
            color = {250, 240, 230}
        },

        {
            keywords = {"maroon"},
            name = "Maroon",
            color = {128, 0, 0}
        },

        {
            keywords = {"mediumaquamarine"},
            name = "Medium Aquamarine",
            color = {102, 205, 170}
        },

        {
            keywords = {"mediumblue"},
            name = "Medium Blue",
            color = {0, 0, 205}
        },

        {
            keywords = {"mediumorchid"},
            name = "Medium Orchid",
            color = {186, 85, 211}
        },

        {
            keywords = {"mediumpurple"},
            name = "Medium Purple",
            color = {147, 112, 219}
        },

        {
            keywords = {"mediumseagreen"},
            name = "Medium Sea Green",
            color = {60, 179, 113}
        },

        {
            keywords = {"mediumslateblue"},
            name = "Medium Slate Blue",
            color = {123, 104, 238}
        },

        {
            keywords = {"mediumspringgreen"},
            name = "Medium Spring Green",
            color = {0, 250, 154}
        },

        {
            keywords = {"mediumturquoise"},
            name = "Medium Turquoise",
            color = {72, 209, 204}
        },

        {
            keywords = {"mediumvioletred"},
            name = "Medium Violet Red",
            color = {199, 21, 133}
        },

        {
            keywords = {"midnightblue"},
            name = "Midnight Blue",
            color = {25, 25, 112}
        },

        {
            keywords = {"mintcream"},
            name = "Mint Cream",
            color = {245, 255, 250}
        },

        {
            keywords = {"mistyrose"},
            name = "Misty Rose",
            color = {255, 228, 225}
        },

        {
            keywords = {"moccasin"},
            name = "Moccasin",
            color = {255, 228, 181}
        },

        {
            keywords = {"navajowhite"},
            name = "Navajo White",
            color = {255, 222, 173}
        },

        {
            keywords = {"navy"},
            name = "Navy",
            color = {0, 0, 128}
        },

        {
            keywords = {"oldlace"},
            name = "Old Lace",
            color = {253, 245, 230}
        },

        {
            keywords = {"olive"},
            name = "Olive",
            color = {128, 128, 0}
        },

        {
            keywords = {"olivedrab"},
            name = "Olive Drab",
            color = {107, 142, 35}
        },

        {
            keywords = {"orange"},
            name = "Orange",
            color = {255, 165, 0}
        },

        {
            keywords = {"orangered"},
            name = "Orange Red",
            color = {255, 69, 0}
        },

        {
            keywords = {"orchid"},
            name = "Orchid",
            color = {218, 112, 214}
        },

        {
            keywords = {"palegoldenrod"},
            name = "Pale Golden Rod",
            color = {238, 232, 170}
        },

        {
            keywords = {"palegreen"},
            name = "Pale Green",
            color = {152, 251, 152}
        },

        {
            keywords = {"paleturquoise"},
            name = "Pale Turquoise",
            color = {175, 238, 238}
        },

        {
            keywords = {"palevioletred"},
            name = "Pale Violet Red",
            color = {219, 112, 147}
        },

        {
            keywords = {"papayawhip"},
            name = "Papaya Whip",
            color = {255, 239, 213}
        },

        {
            keywords = {"peachpuff"},
            name = "Peach Puff",
            color = {255, 218, 185}
        },

        {
            keywords = {"peru"},
            name = "Peru",
            color = {205, 133, 63}
        },

        {
            keywords = {"pink"},
            name = "Pink",
            color = {255, 192, 203}
        },

        {
            keywords = {"plum"},
            name = "Plum",
            color = {221, 160, 221}
        },

        {
            keywords = {"powderblue"},
            name = "Powder Blue",
            color = {176, 224, 230}
        },

        {
            keywords = {"purple"},
            name = "Purple",
            color = {128, 0, 128}
        },

        {
            keywords = {"rebeccapurple"},
            name = "Rebecca Purple",
            color = {102, 51, 153}
        },

        {
            keywords = {"red"},
            name = "Red",
            color = {255, 0, 0}
        },

        {
            keywords = {"rosybrown"},
            name = "Rosy Brown",
            color = {188, 143, 143}
        },

        {
            keywords = {"royalblue"},
            name = "Royal Blue",
            color = {65, 105, 225}
        },

        {
            keywords = {"saddlebrown"},
            name = "Saddle Brown",
            color = {139, 69, 19}
        },

        {
            keywords = {"salmon"},
            name = "Salmon",
            color = {250, 128, 114}
        },

        {
            keywords = {"sandybrown"},
            name = "Sandy Brown",
            color = {244, 164, 96}
        },

        {
            keywords = {"seagreen"},
            name = "Sea Green",
            color = {46, 139, 87}
        },

        {
            keywords = {"seashell"},
            name = "Seashell",
            color = {255, 245, 238}
        },

        {
            keywords = {"sienna"},
            name = "Sienna",
            color = {160, 82, 45}
        },

        {
            keywords = {"silver"},
            name = "Silver",
            color = {192, 192, 192}
        },

        {
            keywords = {"skyblue"},
            name = "Sky Blue",
            color = {135, 206, 235}
        },

        {
            keywords = {"slateblue"},
            name = "Slate Blue",
            color = {106, 90, 205}
        },

        {
            keywords = {"slategray", "slategrey"},
            name = "Slate Gray",
            color = {112, 128, 144}
        },

        {
            keywords = {"snow"},
            name = "Snow",
            color = {255, 250, 250}
        },

        {
            keywords = {"springgreen"},
            name = "Spring Green",
            color = {0, 255, 127}
        },

        {
            keywords = {"steelblue"},
            name = "Steel Blue",
            color = {70, 130, 180}
        },

        {
            keywords = {"tan"},
            name = "Tan",
            color = {210, 180, 140}
        },

        {
            keywords = {"teal"},
            name = "Teal",
            color = {0, 128, 128}
        },

        {
            keywords = {"thistle"},
            name = "Thistle",
            color = {216, 191, 216}
        },

        {
            keywords = {"tomato"},
            name = "Tomato",
            color = {255, 99, 71}
        },

        {
            keywords = {"turquoise"},
            name = "Turquoise",
            color = {64, 224, 208}
        },

        {
            keywords = {"violet"},
            name = "Violet",
            color = {238, 130, 238}
        },

        {
            keywords = {"wheat"},
            name = "Wheat",
            color = {245, 222, 179}
        },

        {
            keywords = {"white"},
            name = "White",
            color = {255, 255, 255}
        },

        {
            keywords = {"whitesmoke"},
            name = "White Smoke",
            color = {245, 245, 245}
        },

        {
            keywords = {"yellow"},
            name = "Yellow",
            color = {255, 255, 0}
        },

        {
            keywords = {"yellowgreen"},
            name = "Yellow Green",
            color = {154, 205, 50}
        }
    }
}


--!strict
-- Provides a common set of signals for editor mouse input

local root = script.Parent.Parent
local Common = root.Common

local CommonIncludes = Common.Includes
local Signal = require(CommonIncludes.Signal)

---

local colorEditorInputBeganSignal: Signal.Signal<InputObject>,
    fireColorEditorInputBegan: Signal.FireSignal<InputObject> = Signal.createSignal()

local colorEditorInputEndedSignal: Signal.Signal<InputObject>,
    fireColorEditorInputEnded: Signal.FireSignal<InputObject> = Signal.createSignal()

local colorEditorMousePositionChanged: Signal.Signal<Vector2>,
    fireColorEditorMousePositionChanged: Signal.FireSignal<Vector2> = Signal.createSignal()

local gradientEditorMousePositionChanged: Signal.Signal<Vector2>,
    fireGradientEditorMousePositionChanged: Signal.FireSignal<Vector2> = Signal.createSignal()

---

local EditorInputSignals = {
    ColorEditor = {
        InputBegan = {
            Event = colorEditorInputBeganSignal,
            Fire = fireColorEditorInputBegan,
        },

        InputEnded = {
            Event = colorEditorInputEndedSignal,
            Fire = fireColorEditorInputEnded,
        },

        MousePositionChanged = {
            Event = colorEditorMousePositionChanged,
            Fire = fireColorEditorMousePositionChanged,
        },
    },

    GradientEditor = {
        MousePositionChanged = {
            Event = gradientEditorMousePositionChanged,
            Fire = fireGradientEditorMousePositionChanged,
        }
    },
}

---

return EditorInputSignals


--!strict
-- Provides enum types

return {
    StoreActionType = {
        SetTheme = "SetTheme",
        UpstreamAvailabilityChanged = "UpstreamAvailabilityChanged",
        UpdateSessionData = "UpdateSessionData",
        UpdateUserData = "UpdateUserData",

        ColorEditor_SetColor = "ColorEditor_SetColor",
        ColorEditor_AddQuickPaletteColor = "ColorEditor_AddQuickPaletteColor",
        ColorEditor_AddPalette = "ColorEditor_AddPalette",
        ColorEditor_RemovePalette = "ColorEditor_RemovePalette",
        ColorEditor_DuplicatePalette = "ColorEditor_DuplicatePalette",
        ColorEditor_ChangePaletteName = "ColorEditor_ChangePaletteName",
        --ColorEditor_AddPaletteColor = "ColorEditor_AddPaletteColor",
        ColorEditor_AddCurrentColorToPalette = "ColorEditor_AddCurrentColorToPalette",
        ColorEditor_RemovePaletteColor = "ColorEditor_RemovePaletteColor",
        ColorEditor_ChangePaletteColorName = "ColorEditor_ChangePaletteColorName",
        ColorEditor_ChangePaletteColorPosition = "ColorEditor_ChangePaletteColorPosition",
        ColorEditor_SetPalettes = "ColorEditor_SetPalettes",

        GradientEditor_ResetState = "GradientEditor_ResetState",
        GradientEditor_SetKeypoints = "GradientEditor_SetKeypoints",
        GradientEditor_SetGradient = "GradientEditor_SetGradient",
        GradientEditor_SetSnapValue = "GradientEditor_SetSnapValue",
        GradientEditor_RemovePaletteGradient = "GradientEditor_RemovePaletteGradient",
        GradientEditor_ChangePaletteGradientName = "GradientEditor_ChangePaletteGradientName",
        GradientEditor_ChangePaletteGradientPosition = "GradientEditor_ChangePaletteGradientPosition",
        GradientEditor_AddPalette = "GradientEditor_AddPalette",
        GradientEditor_RemovePalette = "GradientEditor_RemovePalette",
        GradientEditor_DuplicatePalette = "GradientEditor_DuplicatePalette",
        GradientEditor_ChangePaletteName = "GradientEditor_ChangePaletteName",
        --GradientEditor_AddPaletteGradient = "GradientEditor_AddPaletteGradient",
        GradientEditor_AddCurrentGradientToPalette = "GradientEditor_AddCurrentGradientToPalette",
        GradientEditor_SetPalettes = "GradientEditor_SetPalettes",
    },

    EditorKey = {
        ColorWheel = "ColorWheel",
        RGBSlider = "RGBSlider",
        CMYKSlider = "CMYKSlider",
        HSBSlider = "HSBSlider",
        HSLSlider = "HSLSlider",
        GreyscaleSlider = "GreyscaleSlider",
        KelvinSlider = "KelvinSlider",

        Default = "Default",
    },

    PromptRejection = {
        InvalidPromptOptions = "InvalidPromptOptions",
        PromptAlreadyOpen = "PromptAlreadyOpen",
        ReservationProblem = "ReservationProblem",
        PromptCancelled = "PromptCancelled",
        SameAsInitial = "SameAsInitial",
    }
}


--!strict

local root = script.Parent.Parent
local Common = root.Common

local CommonIncludes = Common.Includes
local Signal = require(CommonIncludes.Signal)

local CommonModules = Common.Modules
local ColorPaneUserDataDefaultValues = require(CommonModules.ColorPaneUserDataDefaultValues)
local ColorPaneUserDataFactory = require(CommonModules.ColorPaneUserDataFactory)
local CommonTypes = require(CommonModules.Types)
local PluginProvider = require(CommonModules.PluginProvider)
local UserData = require(CommonModules.UserData)
local Util = require(CommonModules.Util)

local Modules = root.Modules
local UpstreamUserData = require(Modules.UpstreamUserData)

---

local plugin: Plugin = PluginProvider()

local localUserData: UserData.UserData
local valueChangedSignal: Signal.Signal<CommonTypes.KeyValue>, fireValueChanged: Signal.FireSignal<CommonTypes.KeyValue> = Signal.createSignal()

local localUserDataValueChangedSubscription: Signal.Subscription
local upstreamDataValueChangedSubscription: Signal.Subscription
local availabilityChangedSubscription: Signal.Subscription

---

local ManagedUserData = {}

--[[
    Fires when a user data value changes.
]]
ManagedUserData.ValueChanged = valueChangedSignal

--[[
    Retrives a user data value.

    @param key The name of the value to retrieve
    @return The user data value
]]
ManagedUserData.GetValue = function(key: string)
    if (UpstreamUserData.IsAvailable()) then
        return UpstreamUserData.GetValue(key)
    else
        return localUserData:getValue(key)
    end
end

--[[
    Retrives all user data values.

    @return The values table
]]
ManagedUserData.GetAllValues = function()
    if (UpstreamUserData.IsAvailable()) then
        return UpstreamUserData.GetAllValues()
    else
        return localUserData:getAllValues()
    end
end

--[[
    Updates a user data value.

    @param key The name of the value to update
    @param value The new value of the value
]]
ManagedUserData.SetValue = function(key: string, value: any)
    if (UpstreamUserData.IsAvailable()) then
        UpstreamUserData.SetValue(key, value)
    else
        localUserData:setValue(key, value)
    end
end

---

-- initialise user data
do
    local gotValues: boolean, data: string | CommonTypes.ColorPaneUserData = pcall(UpstreamUserData.GetAllValues)

    if (gotValues) then
        localUserData = ColorPaneUserDataFactory(data::CommonTypes.ColorPaneUserData)
    else
        localUserData = ColorPaneUserDataFactory(Util.table.deepCopy(ColorPaneUserDataDefaultValues))
    end
end

localUserDataValueChangedSubscription = localUserData.valueChanged:subscribe(function(value: CommonTypes.KeyValue)
    if (UpstreamUserData.IsAvailable()) then return end

    fireValueChanged(value)
end)

upstreamDataValueChangedSubscription = UpstreamUserData.ValueChanged:subscribe(function(value: CommonTypes.KeyValue)
    fireValueChanged(value)
end)

availabilityChangedSubscription = UpstreamUserData.AvailabilityChanged:subscribe(function(available: boolean)
    if (not available) then return end

    --  Note: The upstream user data is always authoritative when
    --  pulling data.
    --
    --  If there's a difference between the local and upstream
    --  data, the upstream data will always be used, and local
    --  data will always be overwritten.
    local success, values = pcall(UpstreamUserData.GetAllValues)
    if (not success) then return end

    for k, v in pairs(values) do
        local updated = localUserData:setValue(k, v)

        if (updated) then
            -- we need to fire the event here since the
            -- subscription callback will not
            fireValueChanged({
                Key = k,
                Value = v
            })
        end
    end
end)

plugin.Unloading:Connect(function()
    localUserDataValueChangedSubscription:unsubscribe()
    upstreamDataValueChangedSubscription:unsubscribe()
    availabilityChangedSubscription:unsubscribe()
end)

return ManagedUserData


--!strict
-- Provides the project ID

local HttpService: HttpService = game:GetService("HttpService")

---

local id: string? = nil

--[[
    Provides the project ID.

    If called with an ID, it will be stored for future use.
    If an ID has already been stored, the function will throw an error.

    If called without an ID, it will return the stored ID.
    If an ID has not been stored, the function will set the project ID
        to a random UUID and return that.

    @param newId The ID for this project (optional)
    @return The ID for this project
]]
return function(newId: string?): string
    if (not newId) then
        if (id) then
            return id
        else
            local randomId: string = HttpService:GenerateGUID(false)

            id = randomId
            return randomId
        end
    else
        if (id) then
            error("Project ID already stored")
        end
    end

    id = newId
    return newId
end


--!strict
-- Wrapper for making a callback repeat multiple times

local HttpService: HttpService = game:GetService("HttpService")
local RunService: RunService = game:GetService("RunService")

---

local root = script.Parent.Parent
local Common = root.Common

local CommonModules = Common.Modules
local PluginProvider = require(CommonModules.PluginProvider)

local CommonIncludes = Common.Includes
local Promise = require(CommonIncludes.Promise)

---

local ABSOLUTE_MIN_DELTA: number = 1/60
local DEFAULT_DELTA: number = 0.25

local plugin: Plugin = PluginProvider()
local repeaters = {}

---

local RepeatingCallback = {}

RepeatingCallback.new = function(callback: () -> nil, initialDelta: number?, optionalMinDelta: number?)
    local minDelta: number = optionalMinDelta or ABSOLUTE_MIN_DELTA
    minDelta = if (minDelta >= ABSOLUTE_MIN_DELTA) then minDelta else ABSOLUTE_MIN_DELTA

    local delta: number = initialDelta or DEFAULT_DELTA
    delta = if (delta >= minDelta) then delta else minDelta

    local id: string = HttpService:GenerateGUID()

    local self = setmetatable({
        id = id,
        active = false,
        repeating = false,
        sustained = 0,
        lastRepeat = 0,

        initialDelta = delta,
        minDelta = minDelta,
        actualDelta = delta,

        callback = callback,
    }, { __index = RepeatingCallback })

    repeaters[id] = self
    return self
end

RepeatingCallback.start = function(self)
    self.active = true
    self.delayPromise = Promise.delay(0.5)

    self.delayPromise:andThen(function()
        if (not self.active) then return end

        self.repeating = true
    end):finally(function()
        self.delayPromise = nil
    end)

    self.callback()
end

RepeatingCallback.stop = function(self)
    if (not self.active) then return end

    if (self.delayPromise) then
        self.delayPromise:cancel()
    end

    self.active = false
    self.repeating = false
    self.sustained = 0
    self.actualDelta = self.initialDelta
end

RepeatingCallback.destroy = function(self)
    setmetatable(self, nil)
    repeaters[self.id] = nil
end

---

local repeaterEvent = RunService.Heartbeat:Connect(function(step)
    for _, repeater in pairs(repeaters) do
        if (repeater.repeating) then
            repeater.sustained = repeater.sustained + step

            -- invoke callback
            local now = os.clock()

            if ((now - repeater.lastRepeat) > repeater.actualDelta) then
                repeater.lastRepeat = now
                repeater.callback()
            end

            -- decrease delta
            local sustained = repeater.sustained
            local initialDelta = repeater.initialDelta

            if ((sustained > 1) and (sustained <= 5)) then
                repeater.actualDelta = math.max(initialDelta - (math.log(sustained + 1) * 0.5 * initialDelta), repeater.minDelta)
            end
        end
    end
end)

plugin.Unloading:Connect(function()
    repeaterEvent:Disconnect()
end)

return RepeatingCallback


--!strict

local root = script.Parent.Parent
local Common = root.Common

local CommonModules = Common.Modules
local CommonEnums = require(CommonModules.Enums)
local PluginProvider = require(CommonModules.PluginProvider)

local CommonIncludes = Common.Includes
local Color = require(CommonIncludes.Color).Color
local Rodux = require(CommonIncludes.RoactRodux.Rodux)

local Modules = root.Modules
local Enums = require(Modules.Enums)
local ManagedUserData = require(Modules.ManagedUserData)
local StoreReducers = require(Modules.StoreReducers)
local Types = require(Modules.Types)
local UpstreamUserData = require(Modules.UpstreamUserData)
local Util = require(Modules.Util)

---

type table = Types.table

local Studio: Studio = settings().Studio

local plugin: Plugin = PluginProvider()
local themeChanged: RBXScriptConnection
local valueChangedSubscription
local upstreamAvailabilityChangedSubscription

local convertColorPaletteToColor3s = function(palette: table)
    for i = 1, #palette.colors do
        local color = palette.colors[i]
        local colorValue = color.color

        -- we use Color3s because we only need to display the colors,
        -- not manipulate them
        color.color = Color3.new(table.unpack(colorValue))
    end
end

local convertGradientPalettes = function(palette: table)
    for i = 1, #palette.gradients do
        local gradient = palette.gradients[i]
        local keypoints = gradient.keypoints

        for j = 1, #keypoints do
            local keypoint = keypoints[j]

            keypoints[j] = {
                Time = keypoint.time,
                Color = Color.new(table.unpack(keypoint.color))
            }
        end
    end
end

local initialState: {[any]: any} = {
    theme = Studio.Theme,
    upstreamAvailable = UpstreamUserData.IsAvailable(),

    userData = {
        [CommonEnums.ColorPaneUserDataKey.SnapValue] = ManagedUserData.GetValue(CommonEnums.ColorPaneUserDataKey.SnapValue),
        [CommonEnums.ColorPaneUserDataKey.AskNameBeforePaletteCreation] = ManagedUserData.GetValue(CommonEnums.ColorPaneUserDataKey.AskNameBeforePaletteCreation)
    },

    sessionData = {
        editorPage = 1,
        lastSliderPage = {1, 1},
        lastPalettePage = {1, 1},
        lastToolPage = {1, 1},
        lastHueHarmony = 1,
        paletteLayout = "grid",

        cbDataClass = 1,
        cbNumDataClasses = 3,

        variationSteps = 10,
        colorSorterColors = {},
        picularSearchHistory = {},
    },

    colorEditor = {
        authoritativeEditor = "",

        quickPalette = {},
        palettes = {},
    },

    gradientEditor = {
        palettes = {},
    }
}

---

-- we need to convert the palettes to use the correct formats
do
    local userColorPalettes = ManagedUserData.GetValue(CommonEnums.ColorPaneUserDataKey.UserColorPalettes)
    local userGradientPalettes = ManagedUserData.GetValue(CommonEnums.ColorPaneUserDataKey.UserGradientPalettes)

    for i = 1, #userColorPalettes do
        convertColorPaletteToColor3s(userColorPalettes[i])
    end

    for i = 1, #userGradientPalettes do
        convertGradientPalettes(userGradientPalettes[i])
    end

    initialState.colorEditor.palettes = userColorPalettes
    initialState.gradientEditor.palettes = userGradientPalettes
end

--[[
    ColorPane store
]]
local Store = Rodux.Store.new(Rodux.createReducer(initialState, StoreReducers))

---

themeChanged = Studio.ThemeChanged:Connect(function()
    Store:dispatch({
        type = Enums.StoreActionType.SetTheme,
        theme = Studio.Theme,
    })
end)

valueChangedSubscription = ManagedUserData.ValueChanged:subscribe(function(value)
    local key: string = value.Key

    if (
        (key == CommonEnums.ColorPaneUserDataKey.SnapValue) or
        (key == CommonEnums.ColorPaneUserDataKey.AskNameBeforePaletteCreation)
    ) then
        Store:dispatch({
            type = Enums.StoreActionType.UpdateUserData,
            key = key,
            value = value.Value,
        })
    elseif (key == CommonEnums.ColorPaneUserDataKey.UserColorPalettes) then
        local palettes: table = Util.table.deepCopy(value.Value)

        for i = 1, #palettes do
            convertColorPaletteToColor3s(palettes[i])
        end

        Store:dispatch({
            type = Enums.StoreActionType.ColorEditor_SetPalettes,
            palettes = palettes,
        })
    elseif (key == CommonEnums.ColorPaneUserDataKey.UserGradientPalettes) then
        local palettes: table = Util.table.deepCopy(value.Value)

        for i = 1, #palettes do
            convertGradientPalettes(palettes[i])
        end

        Store:dispatch({
            type = Enums.StoreActionType.GradientEditor_SetPalettes,
            palettes = palettes,
        })
    end
end)

upstreamAvailabilityChangedSubscription = UpstreamUserData.AvailabilityChanged:subscribe(function(available: boolean)
    Store:dispatch({
        type = Enums.StoreActionType.UpstreamAvailabilityChanged,
        available = available,
    })
end)

plugin.Unloading:Connect(function()
    themeChanged:Disconnect()
    valueChangedSubscription:unsubscribe()
    upstreamAvailabilityChangedSubscription:unsubscribe()
end)

return Store