From 315c68f26fe78be3abb2b973d6bf04262beb5fbb Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 3 Jun 2022 16:03:17 -0300 Subject: [PATCH 001/104] Update graphql types --- src/elm/Cambiatus/Mutation.elm | 65 +++++++++++++++++- src/elm/Cambiatus/Object.elm | 4 ++ src/elm/Cambiatus/Object/Category.elm | 95 ++++++++++++++++++++++++++ src/elm/Cambiatus/Object/Community.elm | 7 ++ src/elm/Cambiatus/Object/Product.elm | 7 ++ src/elm/Shop.elm | 3 + 6 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 src/elm/Cambiatus/Object/Category.elm diff --git a/src/elm/Cambiatus/Mutation.elm b/src/elm/Cambiatus/Mutation.elm index 9e2b7265d..4bb5c7977 100644 --- a/src/elm/Cambiatus/Mutation.elm +++ b/src/elm/Cambiatus/Mutation.elm @@ -47,6 +47,47 @@ addCommunityPhotos requiredArgs object_ = Object.selectionForCompositeField "addCommunityPhotos" [ Argument.required "symbol" requiredArgs.symbol Encode.string, Argument.required "urls" requiredArgs.urls (Encode.string |> Encode.list) ] object_ (identity >> Decode.nullable) +type alias CategoryOptionalArguments = + { categories : OptionalArgument (List Int) + , categoryId : OptionalArgument String + , iconUri : OptionalArgument String + , id : OptionalArgument Int + , imageUri : OptionalArgument String + , metaDescription : OptionalArgument String + , metaKeywords : OptionalArgument String + , metaTitle : OptionalArgument String + , slug : OptionalArgument String + } + + +type alias CategoryRequiredArguments = + { description : String + , name : String + } + + +{-| [Auth required - Admin only] Upserts a category + + - categoryId - Parent category ID + +-} +category : + (CategoryOptionalArguments -> CategoryOptionalArguments) + -> CategoryRequiredArguments + -> SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe decodesTo) RootMutation +category fillInOptionals requiredArgs object_ = + let + filledInOptionals = + fillInOptionals { categories = Absent, categoryId = Absent, iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, slug = Absent } + + optionalArgs = + [ Argument.optional "categories" filledInOptionals.categories (Encode.int |> Encode.list), Argument.optional "categoryId" filledInOptionals.categoryId Encode.string, Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "slug" filledInOptionals.slug Encode.string ] + |> List.filterMap identity + in + Object.selectionForCompositeField "category" (optionalArgs ++ [ Argument.required "description" requiredArgs.description Encode.string, Argument.required "name" requiredArgs.name Encode.string ]) object_ (identity >> Decode.nullable) + + type alias CommunityRequiredArguments = { communityId : String , input : Cambiatus.InputObject.CommunityUpdateInput @@ -103,6 +144,20 @@ deleteAddress object_ = Object.selectionForCompositeField "deleteAddress" [] object_ (identity >> Decode.nullable) +type alias DeleteCategoryRequiredArguments = + { id : Int } + + +{-| [Auth required - Admin only] Upserts a category +-} +deleteCategory : + DeleteCategoryRequiredArguments + -> SelectionSet decodesTo Cambiatus.Object.DeleteStatus + -> SelectionSet (Maybe decodesTo) RootMutation +deleteCategory requiredArgs object_ = + Object.selectionForCompositeField "deleteCategory" [ Argument.required "id" requiredArgs.id Encode.int ] object_ (identity >> Decode.nullable) + + {-| [Auth required] A mutation to delete user's kyc data -} deleteKyc : @@ -240,7 +295,8 @@ preference fillInOptionals object_ = type alias ProductOptionalArguments = - { communityId : OptionalArgument String + { categories : OptionalArgument (List Int) + , communityId : OptionalArgument String , description : OptionalArgument String , id : OptionalArgument Int , images : OptionalArgument (List String) @@ -252,6 +308,9 @@ type alias ProductOptionalArguments = {-| [Auth required] Upserts a product + + - categories - List of categories ID you want to relate to this product + -} product : (ProductOptionalArguments -> ProductOptionalArguments) @@ -260,10 +319,10 @@ product : product fillInOptionals object_ = let filledInOptionals = - fillInOptionals { communityId = Absent, description = Absent, id = Absent, images = Absent, price = Absent, title = Absent, trackStock = Absent, units = Absent } + fillInOptionals { categories = Absent, communityId = Absent, description = Absent, id = Absent, images = Absent, price = Absent, title = Absent, trackStock = Absent, units = Absent } optionalArgs = - [ Argument.optional "communityId" filledInOptionals.communityId Encode.string, Argument.optional "description" filledInOptionals.description Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "images" filledInOptionals.images (Encode.string |> Encode.list), Argument.optional "price" filledInOptionals.price Encode.float, Argument.optional "title" filledInOptionals.title Encode.string, Argument.optional "trackStock" filledInOptionals.trackStock Encode.bool, Argument.optional "units" filledInOptionals.units Encode.int ] + [ Argument.optional "categories" filledInOptionals.categories (Encode.int |> Encode.list), Argument.optional "communityId" filledInOptionals.communityId Encode.string, Argument.optional "description" filledInOptionals.description Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "images" filledInOptionals.images (Encode.string |> Encode.list), Argument.optional "price" filledInOptionals.price Encode.float, Argument.optional "title" filledInOptionals.title Encode.string, Argument.optional "trackStock" filledInOptionals.trackStock Encode.bool, Argument.optional "units" filledInOptionals.units Encode.int ] |> List.filterMap identity in Object.selectionForCompositeField "product" optionalArgs object_ (identity >> Decode.nullable) diff --git a/src/elm/Cambiatus/Object.elm b/src/elm/Cambiatus/Object.elm index 1bf93d0b9..a0903ea14 100644 --- a/src/elm/Cambiatus/Object.elm +++ b/src/elm/Cambiatus/Object.elm @@ -13,6 +13,10 @@ type Address = Address +type Category + = Category + + type Check = Check diff --git a/src/elm/Cambiatus/Object/Category.elm b/src/elm/Cambiatus/Object/Category.elm new file mode 100644 index 000000000..1e444cfb6 --- /dev/null +++ b/src/elm/Cambiatus/Object/Category.elm @@ -0,0 +1,95 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Cambiatus.Object.Category exposing (..) + +import Cambiatus.InputObject +import Cambiatus.Interface +import Cambiatus.Object +import Cambiatus.Scalar +import Cambiatus.ScalarCodecs +import Cambiatus.Union +import Graphql.Internal.Builder.Argument as Argument exposing (Argument) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) +import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.SelectionSet exposing (SelectionSet) +import Json.Decode as Decode + + +categories : + SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe (List decodesTo)) Cambiatus.Object.Category +categories object_ = + Object.selectionForCompositeField "categories" [] object_ (identity >> Decode.list >> Decode.nullable) + + +category : + SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe decodesTo) Cambiatus.Object.Category +category object_ = + Object.selectionForCompositeField "category" [] object_ (identity >> Decode.nullable) + + +description : SelectionSet String Cambiatus.Object.Category +description = + Object.selectionForField "String" "description" [] Decode.string + + +iconUri : SelectionSet (Maybe String) Cambiatus.Object.Category +iconUri = + Object.selectionForField "(Maybe String)" "iconUri" [] (Decode.string |> Decode.nullable) + + +id : SelectionSet Int Cambiatus.Object.Category +id = + Object.selectionForField "Int" "id" [] Decode.int + + +imageUri : SelectionSet (Maybe String) Cambiatus.Object.Category +imageUri = + Object.selectionForField "(Maybe String)" "imageUri" [] (Decode.string |> Decode.nullable) + + +insertedAt : SelectionSet Cambiatus.ScalarCodecs.NaiveDateTime Cambiatus.Object.Category +insertedAt = + Object.selectionForField "ScalarCodecs.NaiveDateTime" "insertedAt" [] (Cambiatus.ScalarCodecs.codecs |> Cambiatus.Scalar.unwrapCodecs |> .codecNaiveDateTime |> .decoder) + + +metaDescription : SelectionSet (Maybe String) Cambiatus.Object.Category +metaDescription = + Object.selectionForField "(Maybe String)" "metaDescription" [] (Decode.string |> Decode.nullable) + + +metaKeywords : SelectionSet (Maybe String) Cambiatus.Object.Category +metaKeywords = + Object.selectionForField "(Maybe String)" "metaKeywords" [] (Decode.string |> Decode.nullable) + + +metaTitle : SelectionSet (Maybe String) Cambiatus.Object.Category +metaTitle = + Object.selectionForField "(Maybe String)" "metaTitle" [] (Decode.string |> Decode.nullable) + + +name : SelectionSet String Cambiatus.Object.Category +name = + Object.selectionForField "String" "name" [] Decode.string + + +products : + SelectionSet decodesTo Cambiatus.Object.Product + -> SelectionSet (Maybe (List decodesTo)) Cambiatus.Object.Category +products object_ = + Object.selectionForCompositeField "products" [] object_ (identity >> Decode.list >> Decode.nullable) + + +slug : SelectionSet (Maybe String) Cambiatus.Object.Category +slug = + Object.selectionForField "(Maybe String)" "slug" [] (Decode.string |> Decode.nullable) + + +updatedAt : SelectionSet Cambiatus.ScalarCodecs.NaiveDateTime Cambiatus.Object.Category +updatedAt = + Object.selectionForField "ScalarCodecs.NaiveDateTime" "updatedAt" [] (Cambiatus.ScalarCodecs.codecs |> Cambiatus.Scalar.unwrapCodecs |> .codecNaiveDateTime |> .decoder) diff --git a/src/elm/Cambiatus/Object/Community.elm b/src/elm/Cambiatus/Object/Community.elm index 408863a38..40a0eaf02 100644 --- a/src/elm/Cambiatus/Object/Community.elm +++ b/src/elm/Cambiatus/Object/Community.elm @@ -30,6 +30,13 @@ autoInvite = Object.selectionForField "Bool" "autoInvite" [] Decode.bool +categories : + SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (List decodesTo) Cambiatus.Object.Community +categories object_ = + Object.selectionForCompositeField "categories" [] object_ (identity >> Decode.list) + + claimCount : SelectionSet Int Cambiatus.Object.Community claimCount = Object.selectionForField "Int" "claimCount" [] Decode.int diff --git a/src/elm/Cambiatus/Object/Product.elm b/src/elm/Cambiatus/Object/Product.elm index 14648d270..7022e874e 100644 --- a/src/elm/Cambiatus/Object/Product.elm +++ b/src/elm/Cambiatus/Object/Product.elm @@ -19,6 +19,13 @@ import Graphql.SelectionSet exposing (SelectionSet) import Json.Decode as Decode +categories : + SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (List decodesTo) Cambiatus.Object.Product +categories object_ = + Object.selectionForCompositeField "categories" [] object_ (identity >> Decode.list) + + community : SelectionSet decodesTo Cambiatus.Object.Community -> SelectionSet decodesTo Cambiatus.Object.Product diff --git a/src/elm/Shop.elm b/src/elm/Shop.elm index 8d634c5ce..dc6b33bda 100755 --- a/src/elm/Shop.elm +++ b/src/elm/Shop.elm @@ -322,6 +322,9 @@ upsert { id, symbol, title, description, images, price, stockTracking } = Just (Id unwrappedId) -> Present unwrappedId + + -- TODO - Include categories here + , categories = Absent , communityId = Present (Eos.symbolToString symbol) , title = Present title , description = Present (Markdown.toRawString description) From 8ddc4e1422b01eb6dab4d0bc28bac7ff95abe976 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 6 Jun 2022 11:46:36 -0300 Subject: [PATCH 002/104] Create route and page for categories settings --- src/elm/Main.elm | 22 ++++++ .../Community/Settings/Shop/Categories.elm | 68 +++++++++++++++++++ src/elm/Route.elm | 5 ++ src/elm/Session/LoggedIn.elm | 1 + 4 files changed, 96 insertions(+) create mode 100644 src/elm/Page/Community/Settings/Shop/Categories.elm diff --git a/src/elm/Main.elm b/src/elm/Main.elm index bc51fe22e..6ffa1a454 100755 --- a/src/elm/Main.elm +++ b/src/elm/Main.elm @@ -27,6 +27,7 @@ import Page.Community.Settings.News.Editor as CommunitySettingsNewsEditor import Page.Community.Settings.ObjectiveEditor as CommunitySettingsObjectiveEditor import Page.Community.Settings.Objectives as CommunitySettingsObjectives import Page.Community.Settings.Settings as CommunitySettings +import Page.Community.Settings.Shop.Categories as CommunitySettingsShopCategories import Page.Community.Settings.Sponsorship as CommunitySettingsSponsorship import Page.Community.Settings.Sponsorship.Fiat as CommunitySettingsSponsorshipFiat import Page.Community.Settings.Sponsorship.ThankYouMessage as CommunitySettingsSponsorshipThankYouMessage @@ -170,6 +171,7 @@ type Status | CommunityObjectives CommunityObjectives.Model | CommunityEditor CommunityEditor.Model | CommunitySettings CommunitySettings.Model + | CommunitySettingsShopCategories CommunitySettingsShopCategories.Model | CommunitySettingsFeatures CommunitySettingsFeatures.Model | CommunitySettingsInfo CommunitySettingsInfo.Model | CommunitySettingsNews CommunitySettingsNews.Model @@ -223,6 +225,7 @@ type Msg | GotCommunityObjectivesMsg CommunityObjectives.Msg | GotCommunityEditorMsg CommunityEditor.Msg | GotCommunitySettingsMsg CommunitySettings.Msg + | GotCommunitySettingsShopCategoriesMsg CommunitySettingsShopCategories.Msg | GotCommunitySettingsFeaturesMsg CommunitySettingsFeatures.Msg | GotCommunitySettingsInfoMsg CommunitySettingsInfo.Msg | GotCommunitySettingsNewsMsg CommunitySettingsNews.Msg @@ -467,6 +470,11 @@ update msg model = >> updateLoggedInUResult CommunitySettings GotCommunitySettingsMsg model |> withLoggedIn + ( GotCommunitySettingsShopCategoriesMsg subMsg, CommunitySettingsShopCategories subModel ) -> + CommunitySettingsShopCategories.update subMsg subModel + >> updateLoggedInUResult CommunitySettingsShopCategories GotCommunitySettingsShopCategoriesMsg model + |> withLoggedIn + ( GotCommunitySettingsFeaturesMsg subMsg, CommunitySettingsFeatures subModel ) -> CommunitySettingsFeatures.update subMsg subModel >> updateLoggedInUResult CommunitySettingsFeatures GotCommunitySettingsFeaturesMsg model @@ -1048,6 +1056,9 @@ statusToRoute status session = CommunitySettings _ -> Just Route.CommunitySettings + CommunitySettingsShopCategories _ -> + Just Route.CommunitySettingsShopCategories + CommunitySettingsFeatures _ -> Just Route.CommunitySettingsFeatures @@ -1444,6 +1455,11 @@ changeRouteTo maybeRoute model = >> updateStatusWith CommunitySettings GotCommunitySettingsMsg model |> withLoggedIn Route.CommunitySettings + Just Route.CommunitySettingsShopCategories -> + CommunitySettingsShopCategories.init + >> updateStatusWith CommunitySettingsShopCategories GotCommunitySettingsShopCategoriesMsg model + |> withLoggedIn Route.CommunitySettingsShopCategories + Just Route.CommunitySettingsFeatures -> CommunitySettingsFeatures.init >> updateStatusWith CommunitySettingsFeatures GotCommunitySettingsFeaturesMsg model @@ -1727,6 +1743,9 @@ msgToString msg = GotCommunitySettingsMsg subMsg -> "GotCommunitySettingsMsg" :: CommunitySettings.msgToString subMsg + GotCommunitySettingsShopCategoriesMsg subMsg -> + "GotCommunitySettingsShopCategoriesMsg" :: CommunitySettingsShopCategories.msgToString subMsg + GotCommunitySettingsFeaturesMsg subMsg -> "GotCommunitySettingsFeaturesMsg" :: CommunitySettingsFeatures.msgToString subMsg @@ -1957,6 +1976,9 @@ view model = CommunitySettings subModel -> viewLoggedIn subModel LoggedIn.CommunitySettings GotCommunitySettingsMsg CommunitySettings.view + CommunitySettingsShopCategories subModel -> + viewLoggedIn subModel LoggedIn.CommunitySettingsShopCategories GotCommunitySettingsShopCategoriesMsg CommunitySettingsShopCategories.view + CommunitySettingsFeatures subModel -> viewLoggedIn subModel LoggedIn.CommunitySettingsFeatures GotCommunitySettingsFeaturesMsg CommunitySettingsFeatures.view diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm new file mode 100644 index 000000000..6b26e5449 --- /dev/null +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -0,0 +1,68 @@ +module Page.Community.Settings.Shop.Categories exposing + ( Model + , Msg + , init + , msgToString + , update + , view + ) + +import Html exposing (Html, text) +import Session.LoggedIn as LoggedIn +import UpdateResult as UR + + + +-- MODEL + + +type alias Model = + {} + + +init : LoggedIn.Model -> ( Model, Cmd Msg ) +init _ = + ( {}, Cmd.none ) + + + +-- TYPES + + +type Msg + = NoOp + + +type alias UpdateResult = + UR.UpdateResult Model Msg (LoggedIn.External Msg) + + + +-- UPDATE + + +update : Msg -> Model -> LoggedIn.Model -> UpdateResult +update msg model loggedIn = + case msg of + NoOp -> + UR.init model + + + +-- VIEW + + +view : LoggedIn.Model -> Model -> { title : String, content : Html Msg } +view loggedIn model = + { title = "TODO", content = text "TODO" } + + + +-- UTILS + + +msgToString : Msg -> List String +msgToString msg = + case msg of + NoOp -> + [ "NoOp" ] diff --git a/src/elm/Route.elm b/src/elm/Route.elm index 23159ac92..990f724af 100755 --- a/src/elm/Route.elm +++ b/src/elm/Route.elm @@ -78,6 +78,7 @@ type Route | CommunitySettingsNewAction Int | CommunitySettingsEditAction Int Int | CommunitySettingsContacts + | CommunitySettingsShopCategories | CommunitySelector (Maybe Route) | CommunityThankYou | CommunitySponsor @@ -190,6 +191,7 @@ parser url = , Url.map CommunitySettingsNewAction (s "community" s "settings" s "objectives" int s "action" s "new") , Url.map CommunitySettingsEditAction (s "community" s "settings" s "objectives" int s "action" int s "edit") , Url.map CommunitySettingsContacts (s "community" s "settings" s "contacts") + , Url.map CommunitySettingsShopCategories (s "community" s "settings" s "shop" s "categories") , Url.map Claim (s "objectives" int s "action" int s "claim" int) , Url.map Shop (s "shop" @@ -638,6 +640,9 @@ routeToString route = CommunitySettingsContacts -> ( [ "community", "settings", "contacts" ], [] ) + CommunitySettingsShopCategories -> + ( [ "community", "settings", "shop", "categories" ], [] ) + Claim objectiveId actionId claimId -> ( [ "objectives" , String.fromInt objectiveId diff --git a/src/elm/Session/LoggedIn.elm b/src/elm/Session/LoggedIn.elm index 1c3476178..e3b35cc62 100755 --- a/src/elm/Session/LoggedIn.elm +++ b/src/elm/Session/LoggedIn.elm @@ -325,6 +325,7 @@ type Page | CommunityAbout | CommunityObjectives | CommunitySettings + | CommunitySettingsShopCategories | CommunitySettingsInfo | CommunitySettingsNews | CommunitySettingsNewsEditor From 77b3bfc79ba7d29d2047511d5d0353bfed5f9424 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 6 Jun 2022 22:17:45 -0300 Subject: [PATCH 003/104] Create initial data structures --- elm.json | 3 +- src/elm/Shop/Category.elm | 103 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/elm/Shop/Category.elm diff --git a/elm.json b/elm.json index 66fccb10c..8e443c27b 100755 --- a/elm.json +++ b/elm.json @@ -42,7 +42,8 @@ "rtfeldman/elm-iso8601-date-strings": "1.1.3", "rtfeldman/elm-validate": "4.0.1", "ryannhg/date-format": "2.3.0", - "thaterikperson/elm-strftime": "2.0.2" + "thaterikperson/elm-strftime": "2.0.2", + "zwilias/elm-rosetree": "1.5.0" }, "indirect": { "elm/bytes": "1.0.8", diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm new file mode 100644 index 000000000..d7f108b0f --- /dev/null +++ b/src/elm/Shop/Category.elm @@ -0,0 +1,103 @@ +module Shop.Category exposing (Model, Tree, selectionSet) + +import Cambiatus.Object +import Cambiatus.Object.Category +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) +import Tree + + + +-- MODEL + + +type alias Model = + { id : Id + , name : String + + -- TODO - Should this be `Markdown`? + , description : String + , icon : Maybe String + , image : Maybe String + } + + +selectionSet : SelectionSet Model Cambiatus.Object.Category +selectionSet = + SelectionSet.succeed Model + |> SelectionSet.with idSelectionSet + |> SelectionSet.with Cambiatus.Object.Category.name + |> SelectionSet.with Cambiatus.Object.Category.description + |> SelectionSet.with Cambiatus.Object.Category.iconUri + |> SelectionSet.with Cambiatus.Object.Category.imageUri + + + +-- TREE + + +type alias Tree = + -- TODO - Do we want this type alias? + Tree.Tree Model + + +treeSelectionSet : SelectionSet Tree Cambiatus.Object.Category +treeSelectionSet = + let + unfolder : TreeSeed -> ( Model, List TreeSeed ) + unfolder (TreeSeed root) = + ( root.model, root.children ) + in + treeSeedSelectionSet maxDepth + |> SelectionSet.map (\root -> Tree.unfold unfolder root) + + +type TreeSeed + = TreeSeed + { model : Model + , children : List TreeSeed + } + + +treeSeedSelectionSet : Int -> SelectionSet TreeSeed Cambiatus.Object.Category +treeSeedSelectionSet remainingChildren = + let + childrenSelectionSet = + if remainingChildren == 0 then + SelectionSet.succeed [] + + else + treeSeedSelectionSet (remainingChildren - 1) + |> Cambiatus.Object.Category.categories + |> SelectionSet.map (Maybe.withDefault []) + in + SelectionSet.succeed + (\model children -> + TreeSeed + { model = model + , children = children + } + ) + |> SelectionSet.with selectionSet + |> SelectionSet.with childrenSelectionSet + + + +-- ID + + +type Id + = Id Int + + +idSelectionSet : SelectionSet Id Cambiatus.Object.Category +idSelectionSet = + SelectionSet.map Id Cambiatus.Object.Category.id + + + +-- INTERNAL + + +maxDepth : Int +maxDepth = + 5 From 1c689ea531f4a6bf5bc411ce16ff672de35fc2e7 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 7 Jun 2022 15:36:49 -0300 Subject: [PATCH 004/104] Add categories field to community --- src/elm/Community.elm | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/elm/Community.elm b/src/elm/Community.elm index 6b9593b02..56ec728ee 100755 --- a/src/elm/Community.elm +++ b/src/elm/Community.elm @@ -75,6 +75,7 @@ import Markdown exposing (Markdown) import Profile import RemoteData exposing (RemoteData) import Session.Shared exposing (Shared) +import Shop.Category import Time exposing (Posix) @@ -129,6 +130,7 @@ type alias Model = , uploads : RemoteData (Graphql.Http.Error (List String)) (List String) , website : Maybe String , contacts : List Contact.Valid + , shopCategories : RemoteData (Graphql.Http.Error (List Shop.Category.Tree)) (List Shop.Category.Tree) } @@ -168,6 +170,7 @@ type Field | UploadsField | MembersField | NewsField + | ShopCategoriesField {-| `FieldValue` is useful to wrap results of queries for fields that aren't @@ -181,6 +184,7 @@ type FieldValue | UploadsValue (List String) | MembersValue (List Profile.Minimal) | NewsValue (List Community.News.Model) + | ShopCategories (List Shop.Category.Tree) {-| When we want to extract a field that is not loaded by default with the @@ -225,6 +229,9 @@ setFieldValue fieldValue model = NewsValue news -> { model | news = RemoteData.Success news } + ShopCategories categories -> + { model | shopCategories = RemoteData.Success categories } + setFieldAsLoading : Field -> Model -> Model setFieldAsLoading field model = @@ -244,6 +251,9 @@ setFieldAsLoading field model = NewsField -> { model | news = RemoteData.Loading } + ShopCategoriesField -> + { model | shopCategories = RemoteData.Loading } + isFieldLoading : Field -> Model -> Bool isFieldLoading field model = @@ -263,6 +273,9 @@ isFieldLoading field model = NewsField -> RemoteData.isLoading model.news + ShopCategoriesField -> + RemoteData.isLoading model.shopCategories + maybeFieldValue : Field -> Model -> Maybe FieldValue maybeFieldValue field model = @@ -290,6 +303,11 @@ maybeFieldValue field model = |> RemoteData.toMaybe |> Maybe.map NewsValue + ShopCategoriesField -> + model.shopCategories + |> RemoteData.toMaybe + |> Maybe.map ShopCategories + mergeFields : RemoteData x Model -> Model -> Model mergeFields loadedCommunity newCommunity = @@ -356,6 +374,7 @@ communitySelectionSet = (Community.contacts Contact.selectionSet |> SelectionSet.map (List.filterMap identity) ) + |> SelectionSet.hardcoded RemoteData.NotAsked @@ -407,6 +426,10 @@ selectionSetForField field = Community.news Community.News.selectionSet |> SelectionSet.map NewsValue + ShopCategoriesField -> + Community.categories Shop.Category.treeSelectionSet + |> SelectionSet.map ShopCategories + fieldSelectionSet : Eos.Symbol -> Field -> SelectionSet (Maybe FieldValue) RootQuery fieldSelectionSet symbol field = From 1a04db9ee9d6f752ab345509cbd615c24ae1dc24 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 7 Jun 2022 17:04:02 -0300 Subject: [PATCH 005/104] Use correct Community-Domain header when possible --- src/elm/Api/Graphql.elm | 43 ++++++++++++++++++++++++++++++++++-- src/elm/Session/LoggedIn.elm | 32 +++++++++++++-------------- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/elm/Api/Graphql.elm b/src/elm/Api/Graphql.elm index 2904321db..917a2e044 100755 --- a/src/elm/Api/Graphql.elm +++ b/src/elm/Api/Graphql.elm @@ -3,7 +3,7 @@ module Api.Graphql exposing , signPhrasePort, decodeSignedPhrasePort, Password , signIn, Token, SignInResponse, signUp, SignUpResponse , storeToken, createAbsintheSocket, tokenDecoder - , mutation, query + , mutation, query, loggedInMutation, loggedInQuery , errorToString, isNonExistingCommunityError, isNewsNotFoundError, isAuthError ) @@ -43,7 +43,7 @@ give back an auth token so we can perform further requests # Operations -@docs mutation, query +@docs mutation, query, loggedInMutation, loggedInQuery # Error helpers @@ -238,6 +238,15 @@ query ({ endpoints } as shared) maybeAuthToken query_ toMsg = |> Graphql.Http.send (RemoteData.fromResult >> toMsg) +loggedInQuery : LoggedIn loggedIn community shared endpoints -> Maybe Token -> SelectionSet a RootQuery -> (RemoteData (Graphql.Http.Error a) a -> msg) -> Cmd msg +loggedInQuery ({ shared } as loggedIn) maybeAuthToken query_ toMsg = + query_ + |> Graphql.Http.queryRequest shared.endpoints.graphql + |> withLoggedInCommunityDomain loggedIn + |> withAuthToken maybeAuthToken + |> Graphql.Http.send (RemoteData.fromResult >> toMsg) + + mutation : Shared shared endpoints -> Maybe Token -> SelectionSet a RootMutation -> (RemoteData (Graphql.Http.Error a) a -> msg) -> Cmd msg mutation ({ endpoints } as shared) maybeAuthToken mutation_ toMsg = mutation_ @@ -247,6 +256,15 @@ mutation ({ endpoints } as shared) maybeAuthToken mutation_ toMsg = |> Graphql.Http.send (RemoteData.fromResult >> toMsg) +loggedInMutation : LoggedIn loggedIn community shared endpoints -> Maybe Token -> SelectionSet a RootMutation -> (RemoteData (Graphql.Http.Error a) a -> msg) -> Cmd msg +loggedInMutation ({ shared } as loggedIn) maybeAuthToken mutation_ toMsg = + mutation_ + |> Graphql.Http.mutationRequest shared.endpoints.graphql + |> withLoggedInCommunityDomain loggedIn + |> withAuthToken maybeAuthToken + |> Graphql.Http.send (RemoteData.fromResult >> toMsg) + + -- ERROR UTILS @@ -297,6 +315,17 @@ type alias Shared shared endpoints = } +type alias Community community = + { community | subdomain : String } + + +type alias LoggedIn loggedIn community shared endpoints = + { loggedIn + | shared : Shared shared endpoints + , selectedCommunity : RemoteData (Graphql.Http.Error (Maybe (Community community))) (Community community) + } + + withAuthToken : Maybe Token -> Graphql.Http.Request decodesTo -> Graphql.Http.Request decodesTo withAuthToken maybeToken = case maybeToken of @@ -332,3 +361,13 @@ withCommunityDomain shared = "cambiatus.staging.cambiatus.io" in Graphql.Http.withHeader "Community-Domain" ("https://" ++ communityDomain) + + +withLoggedInCommunityDomain : LoggedIn loggedIn community shared endpoints -> Graphql.Http.Request decodesTo -> Graphql.Http.Request decodesTo +withLoggedInCommunityDomain loggedIn = + case loggedIn.selectedCommunity of + RemoteData.Success { subdomain } -> + Graphql.Http.withHeader "Community-Domain" ("https://" ++ subdomain) + + _ -> + withCommunityDomain loggedIn.shared diff --git a/src/elm/Session/LoggedIn.elm b/src/elm/Session/LoggedIn.elm index e3b35cc62..6e7d56754 100755 --- a/src/elm/Session/LoggedIn.elm +++ b/src/elm/Session/LoggedIn.elm @@ -1320,7 +1320,7 @@ type External msg | SetCommunityField Community.FieldValue | RequiredPrivateKey { successMsg : msg, errorMsg : msg } | RequiredAuthToken { callbackCmd : Api.Graphql.Token -> Cmd msg } - | RequestQuery (Cmd (Result { callbackCmd : Shared -> Api.Graphql.Token -> Cmd msg } msg)) + | RequestQuery (Cmd (Result { callbackCmd : Model -> Api.Graphql.Token -> Cmd msg } msg)) | ShowFeedback Feedback.Status String | HideFeedback | ShowCodeOfConductModal @@ -1343,7 +1343,7 @@ query : -> (RemoteData (Graphql.Http.Error result) result -> msg) -> External msg query model selectionSet toMsg = - graphqlOperation Api.Graphql.query model selectionSet toMsg + graphqlOperation Api.Graphql.loggedInQuery model selectionSet toMsg {-| Perform a GraphQL mutation. This function is preferred over `Api.Graphql.mutation` @@ -1363,11 +1363,11 @@ mutation : -> (RemoteData (Graphql.Http.Error result) result -> msg) -> External msg mutation model selectionSet toMsg = - graphqlOperation Api.Graphql.mutation model selectionSet toMsg + graphqlOperation Api.Graphql.loggedInMutation model selectionSet toMsg graphqlOperation : - (Shared + (Model -> Maybe Api.Graphql.Token -> SelectionSet result typeLock -> (rawOperationResult -> rawOperationResult) @@ -1379,14 +1379,14 @@ graphqlOperation : -> External msg graphqlOperation operation model selectionSet toMsg = let - operationCmd : Shared -> Api.Graphql.Token -> Cmd (RemoteData (Graphql.Http.Error result) result) - operationCmd shared authToken = - operation shared + operationCmd : Model -> Api.Graphql.Token -> Cmd (RemoteData (Graphql.Http.Error result) result) + operationCmd loggedIn authToken = + operation loggedIn (Just authToken) selectionSet identity - treatAuthError : RemoteData (Graphql.Http.Error result) result -> Result { callbackCmd : Shared -> Api.Graphql.Token -> Cmd msg } msg + treatAuthError : RemoteData (Graphql.Http.Error result) result -> Result { callbackCmd : Model -> Api.Graphql.Token -> Cmd msg } msg treatAuthError operationResult = case operationResult of RemoteData.Success success -> @@ -1396,8 +1396,8 @@ graphqlOperation operation model selectionSet toMsg = if Api.Graphql.isAuthError err then Err { callbackCmd = - \newShared -> - operationCmd newShared + \newModel -> + operationCmd newModel >> Cmd.map toMsg } @@ -1411,12 +1411,12 @@ graphqlOperation operation model selectionSet toMsg = Nothing -> RequiredAuthToken { callbackCmd = - operationCmd model.shared + operationCmd model >> Cmd.map toMsg } Just authToken -> - operationCmd model.shared authToken + operationCmd model authToken |> Cmd.map treatAuthError |> RequestQuery @@ -1722,8 +1722,8 @@ mapExternal mapFn msg = Err { callbackCmd } -> Err { callbackCmd = - \shared authToken -> - callbackCmd shared authToken + \model authToken -> + callbackCmd model authToken |> Cmd.map mapFn } @@ -2020,7 +2020,7 @@ type Msg externalMsg | GotAuthTokenPhraseExternal (Api.Graphql.Token -> Cmd externalMsg) (RemoteData (Graphql.Http.Error Api.Graphql.Phrase) Api.Graphql.Phrase) | SignedAuthTokenPhrase Api.Graphql.Password | CompletedGeneratingAuthToken (RemoteData (Graphql.Http.Error Api.Graphql.SignInResponse) Api.Graphql.SignInResponse) - | RequestedQuery (Result { callbackCmd : Shared -> Api.Graphql.Token -> Cmd externalMsg } externalMsg) + | RequestedQuery (Result { callbackCmd : Model -> Api.Graphql.Token -> Cmd externalMsg } externalMsg) | RequestedQueryInternal (Result (Api.Graphql.Token -> Cmd (Msg externalMsg)) (Msg externalMsg)) | ClickedAcceptCodeOfConduct | ClickedDenyCodeOfConduct @@ -2771,7 +2771,7 @@ update msg model = RequestedQuery (Err { callbackCmd }) -> model |> UR.init - |> UR.addMsg (RequestedNewAuthTokenPhraseExternal (callbackCmd model.shared)) + |> UR.addMsg (RequestedNewAuthTokenPhraseExternal (callbackCmd model)) RequestedQueryInternal (Ok resultMsg) -> model From 2aba7f5dbfc9de1c2e4e426663018dcc8f2015e0 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 7 Jun 2022 19:46:06 -0300 Subject: [PATCH 006/104] Implement basic categories view --- elm.json | 3 + src/elm/Main.elm | 2 +- .../Community/Settings/Shop/Categories.elm | 123 +++++++++++++++++- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/elm.json b/elm.json index 8e443c27b..52d624b0e 100755 --- a/elm.json +++ b/elm.json @@ -7,6 +7,7 @@ "dependencies": { "direct": { "CurrySoftware/elm-datepicker": "4.0.0", + "Gizra/elm-all-set": "1.0.1", "NeoVier/elm-mask": "2.0.4", "NoRedInk/elm-json-decode-pipeline": "1.0.0", "NoRedInk/elm-simple-fuzzy": "1.0.3", @@ -32,6 +33,7 @@ "elm-community/string-extra": "4.0.1", "elm-explorations/test": "1.2.2", "fapian/elm-html-aria": "1.4.0", + "hecrj/elm-slug": "1.0.2", "jfmengels/elm-review": "2.3.2", "justinmimbs/date": "3.2.1", "justinmimbs/time-extra": "1.1.0", @@ -52,6 +54,7 @@ "elm-community/json-extra": "4.3.0", "lukewestby/elm-string-interpolate": "1.0.4", "myrho/elm-round": "1.0.4", + "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", "stil4m/elm-syntax": "7.1.3", "stil4m/structured-writer": "1.0.3" diff --git a/src/elm/Main.elm b/src/elm/Main.elm index 6ffa1a454..17b3ce37a 100755 --- a/src/elm/Main.elm +++ b/src/elm/Main.elm @@ -1457,7 +1457,7 @@ changeRouteTo maybeRoute model = Just Route.CommunitySettingsShopCategories -> CommunitySettingsShopCategories.init - >> updateStatusWith CommunitySettingsShopCategories GotCommunitySettingsShopCategoriesMsg model + >> updateLoggedInUResult CommunitySettingsShopCategories GotCommunitySettingsShopCategoriesMsg model |> withLoggedIn Route.CommunitySettingsShopCategories Just Route.CommunitySettingsFeatures -> diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 6b26e5449..190404721 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -7,9 +7,19 @@ module Page.Community.Settings.Shop.Categories exposing , view ) -import Html exposing (Html, text) +import Community +import EverySet exposing (EverySet) +import Html exposing (Html, button, details, li, summary, text, ul) +import Html.Attributes exposing (class) +import Html.Events exposing (onClick) +import Icons +import Page +import RemoteData import Session.LoggedIn as LoggedIn +import Shop.Category +import Tree import UpdateResult as UR +import Utils @@ -17,12 +27,16 @@ import UpdateResult as UR type alias Model = - {} + { expandedCategories : EverySet Shop.Category.Id + } -init : LoggedIn.Model -> ( Model, Cmd Msg ) +init : LoggedIn.Model -> UpdateResult init _ = - ( {}, Cmd.none ) + -- TODO - Should we start them all expanded? + { expandedCategories = EverySet.empty } + |> UR.init + |> UR.addExt (LoggedIn.RequestedReloadCommunityField Community.ShopCategoriesField) @@ -31,6 +45,7 @@ init _ = type Msg = NoOp + | ClickedToggleExpandCategory Shop.Category.Id type alias UpdateResult = @@ -47,6 +62,17 @@ update msg model loggedIn = NoOp -> UR.init model + ClickedToggleExpandCategory categoryId -> + { model + | expandedCategories = + if EverySet.member categoryId model.expandedCategories then + EverySet.remove categoryId model.expandedCategories + + else + EverySet.insert categoryId model.expandedCategories + } + |> UR.init + -- VIEW @@ -54,7 +80,91 @@ update msg model loggedIn = view : LoggedIn.Model -> Model -> { title : String, content : Html Msg } view loggedIn model = - { title = "TODO", content = text "TODO" } + let + title = + "TODO" + in + { title = title + , content = + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.NotAsked -> + Page.fullPageLoading loggedIn.shared + + RemoteData.Loading -> + Page.fullPageLoading loggedIn.shared + + RemoteData.Failure fieldErr -> + case fieldErr of + Community.CommunityError err -> + Page.fullPageGraphQLError title err + + Community.FieldError err -> + Page.fullPageGraphQLError title err + + RemoteData.Success ( community, categories ) -> + view_ model categories + } + + +view_ : Model -> List Shop.Category.Tree -> Html Msg +view_ model categories = + ul [] + (List.map (viewCategoryTree model) categories) + + +viewCategoryTree : Model -> Shop.Category.Tree -> Html Msg +viewCategoryTree model = + Tree.restructure identity (viewCategoryWithChildren model) + + +viewCategory : Shop.Category.Model -> Html msg +viewCategory category = + text category.name + + +viewCategoryWithChildren : Model -> Shop.Category.Model -> List (Html Msg) -> Html Msg +viewCategoryWithChildren model category children = + let + isOpen = + EverySet.member category.id model.expandedCategories + + openArrowClass = + if isOpen then + "" + + else + "-rotate-90" + in + if List.isEmpty children then + li [ class "flex items-center" ] + [ button + [ class "opacity-0 pointer-events-none" + , onClick (ClickedToggleExpandCategory category.id) + ] + [ Icons.arrowDown openArrowClass + ] + , viewCategory category + ] + + else + li [] + [ details + [ if isOpen then + Html.Attributes.attribute "open" "true" + + else + class "" + , Utils.onClickPreventAll NoOp + ] + [ summary [ class "marker-hidden flex items-center" ] + [ button [ onClick (ClickedToggleExpandCategory category.id) ] + [ Icons.arrowDown ("transition-transform " ++ openArrowClass) + ] + , viewCategory category + ] + , ul [ class "ml-4" ] children + ] + ] @@ -66,3 +176,6 @@ msgToString msg = case msg of NoOp -> [ "NoOp" ] + + ClickedToggleExpandCategory _ -> + [ "ClickedToggleExpandCategory" ] From 606e8705fc7241996c9f986bed5e93c8498a89d7 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 7 Jun 2022 23:17:22 -0300 Subject: [PATCH 007/104] Add basic tree view, with support to adding nested and root categories --- src/elm/Main.elm | 4 + .../Community/Settings/Shop/Categories.elm | 368 ++++++++++++++++-- src/elm/Shop/Category.elm | 38 +- 3 files changed, 378 insertions(+), 32 deletions(-) diff --git a/src/elm/Main.elm b/src/elm/Main.elm index 17b3ce37a..bb2b52da7 100755 --- a/src/elm/Main.elm +++ b/src/elm/Main.elm @@ -144,6 +144,10 @@ subscriptions model = CommunitySponsor.subscriptions subModel |> Sub.map GotCommunitySponsorMsg + CommunitySettingsShopCategories subModel -> + CommunitySettingsShopCategories.subscriptions subModel + |> Sub.map GotCommunitySettingsShopCategoriesMsg + _ -> Sub.none ] diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 190404721..253c74dc3 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -3,23 +3,32 @@ module Page.Community.Settings.Shop.Categories exposing , Msg , init , msgToString + , subscriptions , update , view ) import Community import EverySet exposing (EverySet) -import Html exposing (Html, button, details, li, summary, text, ul) -import Html.Attributes exposing (class) +import Form +import Form.Text +import Form.Validate +import Graphql.Http +import Html exposing (Html, button, details, div, li, span, summary, text, ul) +import Html.Attributes exposing (class, classList) import Html.Events exposing (onClick) import Icons +import Json.Decode import Page -import RemoteData +import RemoteData exposing (RemoteData) import Session.LoggedIn as LoggedIn import Shop.Category +import Slug exposing (Slug) +import Translation import Tree import UpdateResult as UR import Utils +import View.Feedback @@ -28,13 +37,18 @@ import Utils type alias Model = { expandedCategories : EverySet Shop.Category.Id + , newCategoryState : NewCategoryState } init : LoggedIn.Model -> UpdateResult init _ = -- TODO - Should we start them all expanded? - { expandedCategories = EverySet.empty } + { expandedCategories = EverySet.empty + + -- TODO - Should we allow multiple editors to be open at once? + , newCategoryState = NotEditing + } |> UR.init |> UR.addExt (LoggedIn.RequestedReloadCommunityField Community.ShopCategoriesField) @@ -43,9 +57,22 @@ init _ = -- TYPES +type NewCategoryState + = NotEditing + | EditingNewCategory + { parent : Maybe Shop.Category.Id + , form : Form.Model NewCategoryFormInput + } + + type Msg = NoOp | ClickedToggleExpandCategory Shop.Category.Id + | ClickedAddCategory (Maybe Shop.Category.Id) + | ClickedCancelAddCategory + | GotAddCategoryFormMsg (Form.Msg NewCategoryFormInput) + | SubmittedAddCategoryForm NewCategoryFormOutput + | FinishedCreatingCategory (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Model)) (Maybe Shop.Category.Model)) type alias UpdateResult = @@ -73,6 +100,91 @@ update msg model loggedIn = } |> UR.init + ClickedAddCategory maybeParentId -> + { model + | newCategoryState = + EditingNewCategory + { parent = maybeParentId + , form = + Form.init + { name = "" + , description = "" + } + } + } + |> UR.init + + ClickedCancelAddCategory -> + { model | newCategoryState = NotEditing } + |> UR.init + + GotAddCategoryFormMsg subMsg -> + case model.newCategoryState of + NotEditing -> + UR.init model + + EditingNewCategory newCategoryData -> + Form.update loggedIn.shared subMsg newCategoryData.form + |> UR.fromChild + (\newForm -> + { model + | newCategoryState = + { newCategoryData | form = newForm } + |> EditingNewCategory + } + ) + GotAddCategoryFormMsg + LoggedIn.addFeedback + model + + SubmittedAddCategoryForm { name, slug, description } -> + let + parentId = + case model.newCategoryState of + NotEditing -> + Nothing + + EditingNewCategory { parent } -> + parent + in + { model + | newCategoryState = + case model.newCategoryState of + NotEditing -> + NotEditing + + EditingNewCategory newCategoryData -> + { newCategoryData | form = Form.withDisabled True newCategoryData.form } + |> EditingNewCategory + } + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.create + { name = name + , description = description + , slug = slug + , icon = Nothing + , image = Nothing + , parentId = parentId + } + ) + FinishedCreatingCategory + ) + + FinishedCreatingCategory (RemoteData.Success _) -> + -- TODO - Should we open the form to create another category? + { model | newCategoryState = NotEditing } + |> UR.init + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Yay!") + + FinishedCreatingCategory (RemoteData.Failure _) -> + UR.init model + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + + FinishedCreatingCategory _ -> + UR.init model + -- VIEW @@ -82,6 +194,7 @@ view : LoggedIn.Model -> Model -> { title : String, content : Html Msg } view loggedIn model = let title = + -- TODO - I18N "TODO" in { title = title @@ -101,20 +214,30 @@ view loggedIn model = Community.FieldError err -> Page.fullPageGraphQLError title err - RemoteData.Success ( community, categories ) -> - view_ model categories + RemoteData.Success ( _, categories ) -> + view_ loggedIn.shared.translators model categories } -view_ : Model -> List Shop.Category.Tree -> Html Msg -view_ model categories = - ul [] - (List.map (viewCategoryTree model) categories) +view_ : Translation.Translators -> Model -> List Shop.Category.Tree -> Html Msg +view_ translators model categories = + div [] + [ ul [] + (List.map + (\category -> + li [] + [ viewCategoryTree translators model category + ] + ) + categories + ) + , viewAddCategory translators model Nothing + ] -viewCategoryTree : Model -> Shop.Category.Tree -> Html Msg -viewCategoryTree model = - Tree.restructure identity (viewCategoryWithChildren model) +viewCategoryTree : Translation.Translators -> Model -> Shop.Category.Tree -> Html Msg +viewCategoryTree translators model = + Tree.restructure identity (viewCategoryWithChildren translators model) viewCategory : Shop.Category.Model -> Html msg @@ -122,8 +245,8 @@ viewCategory category = text category.name -viewCategoryWithChildren : Model -> Shop.Category.Model -> List (Html Msg) -> Html Msg -viewCategoryWithChildren model category children = +viewCategoryWithChildren : Translation.Translators -> Model -> Shop.Category.Model -> List (Html Msg) -> Html Msg +viewCategoryWithChildren translators model category children = let isOpen = EverySet.member category.id model.expandedCategories @@ -135,36 +258,204 @@ viewCategoryWithChildren model category children = else "-rotate-90" in - if List.isEmpty children then - li [ class "flex items-center" ] - [ button - [ class "opacity-0 pointer-events-none" - , onClick (ClickedToggleExpandCategory category.id) - ] - [ Icons.arrowDown openArrowClass - ] - , viewCategory category + li + [ classList [ ( "ml-8", List.isEmpty children ) ] + ] + (if List.isEmpty children then + [ viewCategory category + , viewAddCategory translators model (Just category) ] - else - li [] + else [ details [ if isOpen then Html.Attributes.attribute "open" "true" else class "" - , Utils.onClickPreventAll NoOp ] - [ summary [ class "marker-hidden flex items-center" ] - [ button [ onClick (ClickedToggleExpandCategory category.id) ] - [ Icons.arrowDown ("transition-transform " ++ openArrowClass) + [ summary + [ class "marker-hidden flex items-center" + , Html.Events.preventDefaultOn "click" + (Json.Decode.succeed ( NoOp, True )) + ] + [ button + [ onClick (ClickedToggleExpandCategory category.id) + , classList [ ( "opacity-0 pointer-events-none", List.isEmpty children ) ] + ] + [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) ] , viewCategory category ] - , ul [ class "ml-4" ] children + , div + [ class "ml-4" ] + [ ul [] children + , viewAddCategory translators model (Just category) + ] ] ] + ) + + +viewAddCategory : Translation.Translators -> Model -> Maybe Shop.Category.Model -> Html Msg +viewAddCategory translators model maybeParentCategory = + let + parentId = + Maybe.map .id maybeParentCategory + + viewAddCategoryButton = + button + [ class "flex ml-4 items-center" + , onClick (ClickedAddCategory parentId) + ] + [ Icons.plus "w-3 h-3 mr-2" + , case maybeParentCategory of + Nothing -> + -- TODO - I18N + text "Add new category" + + Just { name } -> + -- TODO - I18N + text ("Add sub-category of " ++ name) + ] + in + case model.newCategoryState of + NotEditing -> + viewAddCategoryButton + + EditingNewCategory newCategoryData -> + if newCategoryData.parent == parentId then + Form.view [ class "ml-4 bg-white border border-black rounded-md p-6" ] + translators + (\submitButton -> + [ div [ class "flex justify-end gap-4" ] + [ button + [ class "button button-secondary" + , onClick ClickedCancelAddCategory + ] + -- TODO - I18N + [ text "Cancel" ] + , submitButton [ class "button button-primary" ] + -- TODO - I18N + [ text "Create" ] + ] + ] + ) + (newCategoryForm translators) + newCategoryData.form + { toMsg = GotAddCategoryFormMsg + , onSubmit = SubmittedAddCategoryForm + } + + else + viewAddCategoryButton + + + +-- FORM + + +type alias NewCategoryFormInput = + { name : String + , description : String + } + + +type alias NewCategoryFormOutput = + { name : String + , slug : Slug + , description : String + } + + +newCategoryForm : Translation.Translators -> Form.Form msg NewCategoryFormInput NewCategoryFormOutput +newCategoryForm translators = + Form.succeed NewCategoryFormOutput + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Name" + , id = "new-category-name-input" + } + |> Form.Text.withContainerAttrs [ class "mb-4" ] + |> Form.textField + { parser = + Form.Validate.succeed + >> Form.Validate.stringLongerThan 2 + >> Form.Validate.custom + (\name -> + case Slug.generate name of + Nothing -> + -- TODO - Show meaningful error? + Err (\_ -> "") + + Just _ -> + Ok name + ) + >> Form.Validate.validate translators + , value = .name + , update = \newName values -> { values | name = newName } + , externalError = always Nothing + } + ) + |> Form.with + ((\{ name } -> + case Slug.generate name of + Nothing -> + -- TODO - Show error message? + Form.arbitrary + (div [ class "mb-10" ] + [ span [ class "label" ] + -- TODO - I18N + [ text "Slug" ] + , span [ class "text-gray-400 italic" ] + -- TODO - I18N + [ text "Insert a name to generate the slug" ] + ] + ) + + Just slug -> + Form.arbitraryWith slug + (div [ class "mb-10" ] + [ span [ class "label" ] + -- TODO - I18N + [ text "Slug" ] + , text (Slug.toString slug) + ] + ) + ) + |> Form.introspect + ) + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Description" + , id = "new-category-description" + } + |> Form.textField + { parser = + Form.Validate.succeed + >> Form.Validate.stringLongerThan 3 + >> Form.Validate.validate translators + , value = .description + , update = \newDescription values -> { values | description = newDescription } + , externalError = always Nothing + } + ) + + + +-- SUBSCRIPTIONS + + +subscriptions : Model -> Sub Msg +subscriptions model = + case model.newCategoryState of + NotEditing -> + Sub.none + + EditingNewCategory _ -> + Utils.escSubscription ClickedCancelAddCategory @@ -179,3 +470,18 @@ msgToString msg = ClickedToggleExpandCategory _ -> [ "ClickedToggleExpandCategory" ] + + ClickedAddCategory _ -> + [ "ClickedAddCategory" ] + + ClickedCancelAddCategory -> + [ "ClickedCancelAddCategory" ] + + GotAddCategoryFormMsg subMsg -> + "GotAddCategoryFormMsg" :: Form.msgToString subMsg + + SubmittedAddCategoryForm _ -> + [ "SubmittedAddCategoryForm" ] + + FinishedCreatingCategory r -> + [ "FinishedCreatingCategory", UR.remoteDataToString r ] diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index d7f108b0f..4345d7d1a 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,8 +1,12 @@ -module Shop.Category exposing (Model, Tree, selectionSet) +module Shop.Category exposing (Id, Model, Tree, create, selectionSet, treeSelectionSet) +import Cambiatus.Mutation import Cambiatus.Object import Cambiatus.Object.Category +import Graphql.Operation exposing (RootMutation) +import Graphql.OptionalArgument as OptionalArgument import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) +import Slug exposing (Slug) import Tree @@ -21,6 +25,38 @@ type alias Model = } +create : + { name : String + , description : String + , slug : Slug + , icon : Maybe String + , image : Maybe String + , parentId : Maybe Id + } + -> SelectionSet (Maybe Model) RootMutation +create { name, slug, description, icon, image, parentId } = + Cambiatus.Mutation.category + (\_ -> + { categories = OptionalArgument.Absent + , categoryId = + parentId + |> Maybe.map (\(Id id) -> String.fromInt id) + |> OptionalArgument.fromMaybe + , iconUri = OptionalArgument.fromMaybe icon + , imageUri = OptionalArgument.fromMaybe image + , id = OptionalArgument.Absent + , metaDescription = OptionalArgument.Absent + , metaKeywords = OptionalArgument.Absent + , metaTitle = OptionalArgument.Absent + , slug = OptionalArgument.Present (Slug.toString slug) + } + ) + { name = name + , description = description + } + selectionSet + + selectionSet : SelectionSet Model Cambiatus.Object.Category selectionSet = SelectionSet.succeed Model From 36e3015fc9e67d7b22a0dd2999c9002c0c2d5cc2 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 8 Jun 2022 10:47:13 -0300 Subject: [PATCH 008/104] Update graphql types --- src/elm/Cambiatus/Mutation.elm | 18 +++++++++--------- src/elm/Cambiatus/Object/Category.elm | 14 +++++++------- src/elm/Shop/Category.elm | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/elm/Cambiatus/Mutation.elm b/src/elm/Cambiatus/Mutation.elm index 4bb5c7977..83973b345 100644 --- a/src/elm/Cambiatus/Mutation.elm +++ b/src/elm/Cambiatus/Mutation.elm @@ -48,27 +48,27 @@ addCommunityPhotos requiredArgs object_ = type alias CategoryOptionalArguments = - { categories : OptionalArgument (List Int) - , categoryId : OptionalArgument String - , iconUri : OptionalArgument String + { iconUri : OptionalArgument String , id : OptionalArgument Int , imageUri : OptionalArgument String , metaDescription : OptionalArgument String , metaKeywords : OptionalArgument String , metaTitle : OptionalArgument String + , parentCategoryId : OptionalArgument Int , slug : OptionalArgument String } type alias CategoryRequiredArguments = - { description : String + { categories : List Int + , description : String , name : String } {-| [Auth required - Admin only] Upserts a category - - categoryId - Parent category ID + - parentCategoryId - Parent category ID -} category : @@ -79,13 +79,13 @@ category : category fillInOptionals requiredArgs object_ = let filledInOptionals = - fillInOptionals { categories = Absent, categoryId = Absent, iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, slug = Absent } + fillInOptionals { iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, parentCategoryId = Absent, slug = Absent } optionalArgs = - [ Argument.optional "categories" filledInOptionals.categories (Encode.int |> Encode.list), Argument.optional "categoryId" filledInOptionals.categoryId Encode.string, Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "slug" filledInOptionals.slug Encode.string ] + [ Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "parentCategoryId" filledInOptionals.parentCategoryId Encode.int, Argument.optional "slug" filledInOptionals.slug Encode.string ] |> List.filterMap identity in - Object.selectionForCompositeField "category" (optionalArgs ++ [ Argument.required "description" requiredArgs.description Encode.string, Argument.required "name" requiredArgs.name Encode.string ]) object_ (identity >> Decode.nullable) + Object.selectionForCompositeField "category" (optionalArgs ++ [ Argument.required "categories" requiredArgs.categories (Encode.int |> Encode.list), Argument.required "description" requiredArgs.description Encode.string, Argument.required "name" requiredArgs.name Encode.string ]) object_ (identity >> Decode.nullable) type alias CommunityRequiredArguments = @@ -148,7 +148,7 @@ type alias DeleteCategoryRequiredArguments = { id : Int } -{-| [Auth required - Admin only] Upserts a category +{-| [Auth required - Admin only] Deletes a category -} deleteCategory : DeleteCategoryRequiredArguments diff --git a/src/elm/Cambiatus/Object/Category.elm b/src/elm/Cambiatus/Object/Category.elm index 1e444cfb6..d1495a551 100644 --- a/src/elm/Cambiatus/Object/Category.elm +++ b/src/elm/Cambiatus/Object/Category.elm @@ -26,13 +26,6 @@ categories object_ = Object.selectionForCompositeField "categories" [] object_ (identity >> Decode.list >> Decode.nullable) -category : - SelectionSet decodesTo Cambiatus.Object.Category - -> SelectionSet (Maybe decodesTo) Cambiatus.Object.Category -category object_ = - Object.selectionForCompositeField "category" [] object_ (identity >> Decode.nullable) - - description : SelectionSet String Cambiatus.Object.Category description = Object.selectionForField "String" "description" [] Decode.string @@ -78,6 +71,13 @@ name = Object.selectionForField "String" "name" [] Decode.string +parentCategory : + SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe decodesTo) Cambiatus.Object.Category +parentCategory object_ = + Object.selectionForCompositeField "parentCategory" [] object_ (identity >> Decode.nullable) + + products : SelectionSet decodesTo Cambiatus.Object.Product -> SelectionSet (Maybe (List decodesTo)) Cambiatus.Object.Category diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 4345d7d1a..ad3d0e781 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -37,10 +37,9 @@ create : create { name, slug, description, icon, image, parentId } = Cambiatus.Mutation.category (\_ -> - { categories = OptionalArgument.Absent - , categoryId = + { parentCategoryId = parentId - |> Maybe.map (\(Id id) -> String.fromInt id) + |> Maybe.map (\(Id id) -> id) |> OptionalArgument.fromMaybe , iconUri = OptionalArgument.fromMaybe icon , imageUri = OptionalArgument.fromMaybe image @@ -53,6 +52,7 @@ create { name, slug, description, icon, image, parentId } = ) { name = name , description = description + , categories = [] } selectionSet From 54d02426c2e3684ca8d9b1f1636562fe78b3ceef Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 8 Jun 2022 12:05:26 -0300 Subject: [PATCH 009/104] Include parentId in Category.Model, remove recursive selectionSet --- src/elm/Community.elm | 2 +- src/elm/Shop/Category.elm | 83 +++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/src/elm/Community.elm b/src/elm/Community.elm index 56ec728ee..ab5479340 100755 --- a/src/elm/Community.elm +++ b/src/elm/Community.elm @@ -427,7 +427,7 @@ selectionSetForField field = |> SelectionSet.map NewsValue ShopCategoriesField -> - Community.categories Shop.Category.treeSelectionSet + Shop.Category.treesSelectionSet Community.categories |> SelectionSet.map ShopCategories diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index ad3d0e781..6d02f8410 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, selectionSet, treeSelectionSet) +module Shop.Category exposing (Id, Model, Tree, create, selectionSet, treesSelectionSet) import Cambiatus.Mutation import Cambiatus.Object @@ -6,6 +6,7 @@ import Cambiatus.Object.Category import Graphql.Operation exposing (RootMutation) import Graphql.OptionalArgument as OptionalArgument import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) +import Maybe.Extra import Slug exposing (Slug) import Tree @@ -22,6 +23,7 @@ type alias Model = , description : String , icon : Maybe String , image : Maybe String + , parentId : Maybe Id } @@ -65,6 +67,7 @@ selectionSet = |> SelectionSet.with Cambiatus.Object.Category.description |> SelectionSet.with Cambiatus.Object.Category.iconUri |> SelectionSet.with Cambiatus.Object.Category.imageUri + |> SelectionSet.with (Cambiatus.Object.Category.parentCategory idSelectionSet) @@ -76,45 +79,42 @@ type alias Tree = Tree.Tree Model -treeSelectionSet : SelectionSet Tree Cambiatus.Object.Category -treeSelectionSet = +treesSelectionSet : + (SelectionSet Model Cambiatus.Object.Category -> SelectionSet (List Model) typeLock) + -> SelectionSet (List Tree) typeLock +treesSelectionSet categoriesSelectionSet = let - unfolder : TreeSeed -> ( Model, List TreeSeed ) - unfolder (TreeSeed root) = - ( root.model, root.children ) + isRoot : Model -> Bool + isRoot category = + Maybe.Extra.isNothing category.parentId + + createTree : List Model -> Model -> Tree + createTree children root = + Tree.tree root + (List.filterMap + (\child -> + if child.parentId == Just root.id then + Just (createTree children child) + + else + Nothing + ) + children + ) in - treeSeedSelectionSet maxDepth - |> SelectionSet.map (\root -> Tree.unfold unfolder root) - - -type TreeSeed - = TreeSeed - { model : Model - , children : List TreeSeed - } + categoriesSelectionSet selectionSet + |> SelectionSet.map + (\allCategories -> + List.filterMap + (\category -> + if isRoot category then + Just (createTree allCategories category) - -treeSeedSelectionSet : Int -> SelectionSet TreeSeed Cambiatus.Object.Category -treeSeedSelectionSet remainingChildren = - let - childrenSelectionSet = - if remainingChildren == 0 then - SelectionSet.succeed [] - - else - treeSeedSelectionSet (remainingChildren - 1) - |> Cambiatus.Object.Category.categories - |> SelectionSet.map (Maybe.withDefault []) - in - SelectionSet.succeed - (\model children -> - TreeSeed - { model = model - , children = children - } - ) - |> SelectionSet.with selectionSet - |> SelectionSet.with childrenSelectionSet + else + Nothing + ) + allCategories + ) @@ -128,12 +128,3 @@ type Id idSelectionSet : SelectionSet Id Cambiatus.Object.Category idSelectionSet = SelectionSet.map Id Cambiatus.Object.Category.id - - - --- INTERNAL - - -maxDepth : Int -maxDepth = - 5 From 4e6b8619e961790ebe8da14f070ab9e6390a2eed Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 8 Jun 2022 22:48:43 -0300 Subject: [PATCH 010/104] Better tree view, highlight parents when hovering --- .../Community/Settings/Shop/Categories.elm | 120 ++++++++++-------- src/styles/main.css | 8 ++ 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 253c74dc3..854108bea 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -221,17 +221,19 @@ view loggedIn model = view_ : Translation.Translators -> Model -> List Shop.Category.Tree -> Html Msg view_ translators model categories = - div [] - [ ul [] - (List.map - (\category -> - li [] - [ viewCategoryTree translators model category - ] + div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] + [ div [ class "bg-white container mx-auto pt-6 pb-7 px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] + [ ul [ class "mb-2" ] + (List.map + (\category -> + li [] + [ viewCategoryTree translators model category + ] + ) + categories ) - categories - ) - , viewAddCategory translators model Nothing + , viewAddCategory translators [ class "w-full pl-2" ] model Nothing + ] ] @@ -258,57 +260,53 @@ viewCategoryWithChildren translators model category children = else "-rotate-90" in - li - [ classList [ ( "ml-8", List.isEmpty children ) ] - ] - (if List.isEmpty children then - [ viewCategory category - , viewAddCategory translators model (Just category) + li [] + [ details + [ if isOpen then + Html.Attributes.attribute "open" "true" + + else + class "" + , class "category-details" ] - - else - [ details - [ if isOpen then - Html.Attributes.attribute "open" "true" - - else - class "" + [ summary + [ class "marker-hidden flex items-center rounded-sm" + , Html.Events.preventDefaultOn "click" + (Json.Decode.succeed ( NoOp, True )) ] - [ summary - [ class "marker-hidden flex items-center" - , Html.Events.preventDefaultOn "click" - (Json.Decode.succeed ( NoOp, True )) + [ button + [ onClick (ClickedToggleExpandCategory category.id) + , class "flex items-center w-full" ] - [ button - [ onClick (ClickedToggleExpandCategory category.id) - , classList [ ( "opacity-0 pointer-events-none", List.isEmpty children ) ] - ] - [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) - ] + [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) , viewCategory category ] - , div - [ class "ml-4" ] - [ ul [] children - , viewAddCategory translators model (Just category) + ] + , div [ class "ml-4 flex flex-col mb-4 mt-2" ] + [ ul + [ class "grid gap-y-2" + , classList [ ( "mb-2", not (List.isEmpty children) ) ] ] + children + , viewAddCategory translators [] model (Just category) ] ] - ) + ] -viewAddCategory : Translation.Translators -> Model -> Maybe Shop.Category.Model -> Html Msg -viewAddCategory translators model maybeParentCategory = +viewAddCategory : Translation.Translators -> List (Html.Attribute Msg) -> Model -> Maybe Shop.Category.Model -> Html Msg +viewAddCategory translators attrs model maybeParentCategory = let parentId = Maybe.map .id maybeParentCategory - viewAddCategoryButton = + viewAddCategoryButton customAttrs = button - [ class "flex ml-4 items-center" - , onClick (ClickedAddCategory parentId) - ] - [ Icons.plus "w-3 h-3 mr-2" + (class "flex items-center px-2 h-8 font-bold hover:bg-blue-600/20 rounded-sm" + :: onClick (ClickedAddCategory parentId) + :: customAttrs + ) + [ Icons.plus "w-4 h-4 mr-2" , case maybeParentCategory of Nothing -> -- TODO - I18N @@ -321,11 +319,11 @@ viewAddCategory translators model maybeParentCategory = in case model.newCategoryState of NotEditing -> - viewAddCategoryButton + viewAddCategoryButton attrs EditingNewCategory newCategoryData -> if newCategoryData.parent == parentId then - Form.view [ class "ml-4 bg-white border border-black rounded-md p-6" ] + Form.view (class "bg-white border border-gray-300 rounded-md p-4" :: attrs) translators (\submitButton -> [ div [ class "flex justify-end gap-4" ] @@ -348,7 +346,7 @@ viewAddCategory translators model maybeParentCategory = } else - viewAddCategoryButton + viewAddCategoryButton attrs @@ -386,11 +384,16 @@ newCategoryForm translators = (\name -> case Slug.generate name of Nothing -> - -- TODO - Show meaningful error? + -- Errors are shown below, on the slug field Err (\_ -> "") - Just _ -> - Ok name + Just slug -> + if String.length (Slug.toString slug) < 2 then + -- Errors are shown below, on the slug field + Err (\_ -> "") + + else + Ok name ) >> Form.Validate.validate translators , value = .name @@ -402,15 +405,20 @@ newCategoryForm translators = ((\{ name } -> case Slug.generate name of Nothing -> - -- TODO - Show error message? Form.arbitrary (div [ class "mb-10" ] [ span [ class "label" ] -- TODO - I18N [ text "Slug" ] - , span [ class "text-gray-400 italic" ] - -- TODO - I18N - [ text "Insert a name to generate the slug" ] + , if String.isEmpty name then + span [ class "text-gray-400 italic" ] + -- TODO - I18N + [ text "Insert a name to generate the slug" ] + + else + span [ class "form-error" ] + -- TODO - I18N + [ text "Invalid slug" ] ] ) diff --git a/src/styles/main.css b/src/styles/main.css index 3bf4c1245..ab00d39bd 100755 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -583,5 +583,13 @@ https://tailwindcss.com/docs/adding-new-utilities */ /* End of QuillJS classes */ +/* Categories page */ + +.category-details:hover > summary { + @apply bg-blue-600/20; +} + +/* End of categories page */ + /* https://tailwindcss.com/docs/extracting-components#extracting-css-components-with-apply */ @tailwind utilities; From c380b315da85f7486e9e85e09a9bcd303692d52c Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 8 Jun 2022 22:54:57 -0300 Subject: [PATCH 011/104] Remove unused export --- src/elm/Shop/Category.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 6d02f8410..55c4b7290 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, selectionSet, treesSelectionSet) +module Shop.Category exposing (Id, Model, Tree, create, treesSelectionSet) import Cambiatus.Mutation import Cambiatus.Object From 76cd8e6ebe8a4a8d39b88100288678a76c1bc68e Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 9 Jun 2022 11:03:29 -0300 Subject: [PATCH 012/104] Create parent-* variants --- src/styles/main.css | 8 -------- tailwind.config.js | 13 +++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/styles/main.css b/src/styles/main.css index ab00d39bd..3bf4c1245 100755 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -583,13 +583,5 @@ https://tailwindcss.com/docs/adding-new-utilities */ /* End of QuillJS classes */ -/* Categories page */ - -.category-details:hover > summary { - @apply bg-blue-600/20; -} - -/* End of categories page */ - /* https://tailwindcss.com/docs/extracting-components#extracting-css-components-with-apply */ @tailwind utilities; diff --git a/tailwind.config.js b/tailwind.config.js index 64af50ab7..cf08b815f 100755 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -258,6 +258,19 @@ module.exports = { }) addUtilities(rotateUtilities) + }, + // Create `parent-*` variants. This works similar to `group-*` variants, but + // only works on direct children of the `parent` class. + function ({ addVariant, e }) { + const operations = ['hover'] + + operations.forEach((operation) => { + addVariant(`parent-${operation}`, ({ modifySelectors, separator }) => { + modifySelectors(({ className }) => + `.parent:${operation}>.parent-${operation}${e(separator)}${e(className)}` + ) + }) + }) } ], purge: [ From 6be8b1388d58c8242118d3a8d9b5fee613744b4f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 9 Jun 2022 11:36:21 -0300 Subject: [PATCH 013/104] Add ability to update categories --- .../Community/Settings/Shop/Categories.elm | 302 ++++++++++++++++-- src/elm/Shop/Category.elm | 42 ++- 2 files changed, 306 insertions(+), 38 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 854108bea..a051216aa 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -19,6 +19,7 @@ import Html.Attributes exposing (class, classList) import Html.Events exposing (onClick) import Icons import Json.Decode +import List.Extra import Page import RemoteData exposing (RemoteData) import Session.LoggedIn as LoggedIn @@ -28,7 +29,9 @@ import Translation import Tree import UpdateResult as UR import Utils +import View.Components import View.Feedback +import View.Modal as Modal @@ -38,6 +41,7 @@ import View.Feedback type alias Model = { expandedCategories : EverySet Shop.Category.Id , newCategoryState : NewCategoryState + , categoryModalState : CategoryModalState } @@ -48,6 +52,7 @@ init _ = -- TODO - Should we allow multiple editors to be open at once? , newCategoryState = NotEditing + , categoryModalState = Closed } |> UR.init |> UR.addExt (LoggedIn.RequestedReloadCommunityField Community.ShopCategoriesField) @@ -65,14 +70,24 @@ type NewCategoryState } +type CategoryModalState + = Closed + | Open Shop.Category.Id (Form.Model UpdateCategoryFormInput) + + type Msg = NoOp | ClickedToggleExpandCategory Shop.Category.Id | ClickedAddCategory (Maybe Shop.Category.Id) | ClickedCancelAddCategory + | ClickedCategory Shop.Category.Id + | ClosedCategoryModal | GotAddCategoryFormMsg (Form.Msg NewCategoryFormInput) | SubmittedAddCategoryForm NewCategoryFormOutput | FinishedCreatingCategory (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Model)) (Maybe Shop.Category.Model)) + | GotUpdateCategoryFormMsg (Form.Msg UpdateCategoryFormInput) + | SubmittedUpdateCategoryForm UpdateCategoryFormOutput + | FinishedUpdatingCategory (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Model)) (Maybe Shop.Category.Model)) type alias UpdateResult = @@ -85,6 +100,16 @@ type alias UpdateResult = update : Msg -> Model -> LoggedIn.Model -> UpdateResult update msg model loggedIn = + let + getCategory : Shop.Category.Id -> Maybe Shop.Category.Model + getCategory categoryId = + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + findInTrees (\category -> category.id == categoryId) categories + + _ -> + Nothing + in case msg of NoOp -> UR.init model @@ -118,6 +143,27 @@ update msg model loggedIn = { model | newCategoryState = NotEditing } |> UR.init + ClickedCategory categoryId -> + case getCategory categoryId of + Just category -> + { model + | categoryModalState = + Open categoryId + (Form.init + { name = category.name + , description = category.description + } + ) + } + |> UR.init + + Nothing -> + UR.init model + + ClosedCategoryModal -> + { model | categoryModalState = Closed } + |> UR.init + GotAddCategoryFormMsg subMsg -> case model.newCategoryState of NotEditing -> @@ -164,8 +210,6 @@ update msg model loggedIn = { name = name , description = description , slug = slug - , icon = Nothing - , image = Nothing , parentId = parentId } ) @@ -176,15 +220,73 @@ update msg model loggedIn = -- TODO - Should we open the form to create another category? { model | newCategoryState = NotEditing } |> UR.init + -- TODO - Remove this once we update the UI automatically |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Yay!") FinishedCreatingCategory (RemoteData.Failure _) -> + -- TODO - re-enable form UR.init model + -- TODO - I18N |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") FinishedCreatingCategory _ -> UR.init model + GotUpdateCategoryFormMsg subMsg -> + case model.categoryModalState of + Closed -> + UR.init model + + Open categoryId formModel -> + Form.update loggedIn.shared subMsg formModel + |> UR.fromChild + (\newFormModel -> { model | categoryModalState = Open categoryId newFormModel }) + GotUpdateCategoryFormMsg + LoggedIn.addFeedback + model + + SubmittedUpdateCategoryForm formOutput -> + case getCategory formOutput.id of + Nothing -> + UR.init model + + Just category -> + { model + | categoryModalState = + case model.categoryModalState of + Closed -> + Closed + + Open categoryId formModel -> + Open categoryId (Form.withDisabled True formModel) + } + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.update category + { name = formOutput.name + , slug = formOutput.slug + , description = formOutput.description + } + ) + FinishedUpdatingCategory + ) + + FinishedUpdatingCategory (RemoteData.Success _) -> + { model | categoryModalState = Closed } + |> UR.init + -- TODO - Remove this once we update the UI automatically + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Yay!") + + FinishedUpdatingCategory (RemoteData.Failure _) -> + -- TODO - re-enable form + UR.init model + -- TODO - I18N + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + + FinishedUpdatingCategory _ -> + UR.init model + -- VIEW @@ -198,6 +300,8 @@ view loggedIn model = "TODO" in { title = title + + -- TODO - Add back arrow , content = case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.NotAsked -> @@ -222,7 +326,7 @@ view loggedIn model = view_ : Translation.Translators -> Model -> List Shop.Category.Tree -> Html Msg view_ translators model categories = div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] - [ div [ class "bg-white container mx-auto pt-6 pb-7 px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] + [ div [ class "bg-white container mx-auto pt-6 pb-7 px-4 w-full sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] [ ul [ class "mb-2" ] (List.map (\category -> @@ -234,6 +338,17 @@ view_ translators model categories = ) , viewAddCategory translators [ class "w-full pl-2" ] model Nothing ] + , case model.categoryModalState of + Closed -> + text "" + + Open categoryId formModel -> + case findInTrees (\category -> category.id == categoryId) categories of + Nothing -> + text "" + + Just openCategory -> + viewCategoryModal translators openCategory formModel ] @@ -242,9 +357,14 @@ viewCategoryTree translators model = Tree.restructure identity (viewCategoryWithChildren translators model) -viewCategory : Shop.Category.Model -> Html msg +viewCategory : Shop.Category.Model -> Html Msg viewCategory category = - text category.name + button + [ class "hover:underline" + , Utils.onClickNoBubble (ClickedCategory category.id) + ] + [ text category.name + ] viewCategoryWithChildren : Translation.Translators -> Model -> Shop.Category.Model -> List (Html Msg) -> Html Msg @@ -267,10 +387,10 @@ viewCategoryWithChildren translators model category children = else class "" - , class "category-details" + , class "parent" ] [ summary - [ class "marker-hidden flex items-center rounded-sm" + [ class "marker-hidden flex items-center rounded-sm parent-hover:bg-blue-600/20" , Html.Events.preventDefaultOn "click" (Json.Decode.succeed ( NoOp, True )) ] @@ -326,14 +446,14 @@ viewAddCategory translators attrs model maybeParentCategory = Form.view (class "bg-white border border-gray-300 rounded-md p-4" :: attrs) translators (\submitButton -> - [ div [ class "flex justify-end gap-4" ] + [ div [ class "flex flex-col sm:flex-row justify-end gap-4" ] [ button - [ class "button button-secondary" + [ class "button button-secondary w-full sm:w-40" , onClick ClickedCancelAddCategory ] -- TODO - I18N [ text "Cancel" ] - , submitButton [ class "button button-primary" ] + , submitButton [ class "button button-primary w-full sm:w-40" ] -- TODO - I18N [ text "Create" ] ] @@ -349,6 +469,49 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs +viewCategoryModal : Translation.Translators -> Shop.Category.Model -> Form.Model UpdateCategoryFormInput -> Html Msg +viewCategoryModal translators category formModel = + Modal.initWith + { isVisible = True + , closeMsg = ClosedCategoryModal + } + -- TODO - I18N + |> Modal.withHeader "Editing category" + |> Modal.withBody + [ Form.viewWithoutSubmit [ class "mt-2" ] + translators + (\_ -> []) + (updateCategoryForm translators category.id) + formModel + { toMsg = GotUpdateCategoryFormMsg + } + ] + |> Modal.withFooter + [ div [ class "flex flex-col w-full sm:flex-row gap-4 items-center justify-center" ] + [ button + [ class "button button-secondary w-full sm:w-40" + , onClick ClosedCategoryModal + ] + -- TODO - I18N + [ text "Cancel" ] + , button + [ class "button button-primary w-full sm:w-40" + , onClick + (Form.parse (updateCategoryForm translators category.id) + formModel + { onError = GotUpdateCategoryFormMsg + , onSuccess = SubmittedUpdateCategoryForm + } + ) + ] + -- TODO - I18N + [ text "Save" ] + ] + ] + |> Modal.withSize Modal.Large + |> Modal.toHtml + + -- FORM @@ -368,12 +531,81 @@ type alias NewCategoryFormOutput = newCategoryForm : Translation.Translators -> Form.Form msg NewCategoryFormInput NewCategoryFormOutput newCategoryForm translators = - Form.succeed NewCategoryFormOutput + Form.succeed + (\{ name, slug } description -> + { name = name, slug = slug, description = description } + ) + |> Form.with (nameAndSlugForm translators { nameFieldId = "new-category-name" }) + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Description" + , id = "new-category-description" + } + |> Form.textField + { parser = + Form.Validate.succeed + >> Form.Validate.stringLongerThan 3 + >> Form.Validate.validate translators + , value = .description + , update = \newDescription values -> { values | description = newDescription } + , externalError = always Nothing + } + ) + + +type alias UpdateCategoryFormInput = + { name : String + , description : String + } + + +type alias UpdateCategoryFormOutput = + { id : Shop.Category.Id + , name : String + , slug : Slug + , description : String + } + + +updateCategoryForm : Translation.Translators -> Shop.Category.Id -> Form.Form msg UpdateCategoryFormInput UpdateCategoryFormOutput +updateCategoryForm translators id = + Form.succeed + (\{ name, slug } description -> + { name = name + , slug = slug + , description = description + , id = id + } + ) + |> Form.with (nameAndSlugForm translators { nameFieldId = "update-category-name" }) + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Description" + , id = "update-category-description" + } + |> Form.Text.withContainerAttrs [ class "mb-0" ] + |> Form.textField + { parser = + Form.Validate.succeed + >> Form.Validate.stringLongerThan 3 + >> Form.Validate.validate translators + , value = .description + , update = \newDescription values -> { values | description = newDescription } + , externalError = always Nothing + } + ) + + +nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Slug } +nameAndSlugForm translators { nameFieldId } = + Form.succeed (\name slug -> { name = name, slug = slug }) |> Form.with (Form.Text.init -- TODO - I18N { label = "Name" - , id = "new-category-name-input" + , id = nameFieldId } |> Form.Text.withContainerAttrs [ class "mb-4" ] |> Form.textField @@ -407,9 +639,9 @@ newCategoryForm translators = Nothing -> Form.arbitrary (div [ class "mb-10" ] - [ span [ class "label" ] + [ View.Components.label [] -- TODO - I18N - [ text "Slug" ] + { targetId = nameFieldId, labelText = "Slug" } , if String.isEmpty name then span [ class "text-gray-400 italic" ] -- TODO - I18N @@ -425,31 +657,15 @@ newCategoryForm translators = Just slug -> Form.arbitraryWith slug (div [ class "mb-10" ] - [ span [ class "label" ] + [ View.Components.label [] -- TODO - I18N - [ text "Slug" ] + { targetId = nameFieldId, labelText = "Slug" } , text (Slug.toString slug) ] ) ) |> Form.introspect ) - |> Form.with - (Form.Text.init - -- TODO - I18N - { label = "Description" - , id = "new-category-description" - } - |> Form.textField - { parser = - Form.Validate.succeed - >> Form.Validate.stringLongerThan 3 - >> Form.Validate.validate translators - , value = .description - , update = \newDescription values -> { values | description = newDescription } - , externalError = always Nothing - } - ) @@ -470,6 +686,13 @@ subscriptions model = -- UTILS +findInTrees : (a -> Bool) -> List (Tree.Tree a) -> Maybe a +findInTrees fn trees = + trees + |> List.concatMap Tree.flatten + |> List.Extra.find fn + + msgToString : Msg -> List String msgToString msg = case msg of @@ -485,11 +708,26 @@ msgToString msg = ClickedCancelAddCategory -> [ "ClickedCancelAddCategory" ] + ClickedCategory _ -> + [ "ClickedCategory" ] + + ClosedCategoryModal -> + [ "ClosedCategoryModal" ] + GotAddCategoryFormMsg subMsg -> "GotAddCategoryFormMsg" :: Form.msgToString subMsg + GotUpdateCategoryFormMsg subMsg -> + "GotUpdateCategoryFormMsg" :: Form.msgToString subMsg + SubmittedAddCategoryForm _ -> [ "SubmittedAddCategoryForm" ] FinishedCreatingCategory r -> [ "FinishedCreatingCategory", UR.remoteDataToString r ] + + SubmittedUpdateCategoryForm _ -> + [ "SubmittedUpdateCategoryForm" ] + + FinishedUpdatingCategory r -> + [ "FinishedUpdatingCategory", UR.remoteDataToString r ] diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 55c4b7290..8761e672d 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, treesSelectionSet) +module Shop.Category exposing (Id, Model, Tree, create, treesSelectionSet, update) import Cambiatus.Mutation import Cambiatus.Object @@ -31,20 +31,18 @@ create : { name : String , description : String , slug : Slug - , icon : Maybe String - , image : Maybe String , parentId : Maybe Id } -> SelectionSet (Maybe Model) RootMutation -create { name, slug, description, icon, image, parentId } = +create { name, slug, description, parentId } = Cambiatus.Mutation.category (\_ -> { parentCategoryId = parentId |> Maybe.map (\(Id id) -> id) |> OptionalArgument.fromMaybe - , iconUri = OptionalArgument.fromMaybe icon - , imageUri = OptionalArgument.fromMaybe image + , iconUri = OptionalArgument.Absent + , imageUri = OptionalArgument.Absent , id = OptionalArgument.Absent , metaDescription = OptionalArgument.Absent , metaKeywords = OptionalArgument.Absent @@ -59,6 +57,38 @@ create { name, slug, description, icon, image, parentId } = selectionSet +update : + Model + -> { name : String, description : String, slug : Slug } + -> SelectionSet (Maybe Model) RootMutation +update model { name, description, slug } = + let + unwrapId : Id -> Int + unwrapId (Id id) = + id + in + Cambiatus.Mutation.category + (\_ -> + { parentCategoryId = + model.parentId + |> Maybe.map unwrapId + |> OptionalArgument.fromMaybe + , iconUri = OptionalArgument.fromMaybe model.icon + , imageUri = OptionalArgument.fromMaybe model.image + , id = OptionalArgument.Present (unwrapId model.id) + , metaDescription = OptionalArgument.Absent + , metaKeywords = OptionalArgument.Absent + , metaTitle = OptionalArgument.Absent + , slug = OptionalArgument.Present (Slug.toString slug) + } + ) + { name = name + , description = description + , categories = [] + } + selectionSet + + selectionSet : SelectionSet Model Cambiatus.Object.Category selectionSet = SelectionSet.succeed Model From 4d4c91b12c76d28b988f7c296e882db2d5f7c22f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 9 Jun 2022 14:05:21 -0300 Subject: [PATCH 014/104] Make Icons.trash color configurable --- src/elm/Form/DatePicker.elm | 2 +- src/elm/Form/UserPicker.elm | 2 +- src/elm/Icons.elm | 2 +- src/elm/Profile/Contact.elm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/elm/Form/DatePicker.elm b/src/elm/Form/DatePicker.elm index a9ebfb30c..d3a5d03da 100644 --- a/src/elm/Form/DatePicker.elm +++ b/src/elm/Form/DatePicker.elm @@ -213,7 +213,7 @@ view ((Options options) as wrappedOptions) viewConfig toMsg = , type_ "button" , disabled options.disabled ] - [ Icons.trash "group-hover:opacity-80" ] + [ Icons.trash "text-red group-hover:opacity-80" ] else text "" diff --git a/src/elm/Form/UserPicker.elm b/src/elm/Form/UserPicker.elm index 29e11d16c..d2c36b72e 100644 --- a/src/elm/Form/UserPicker.elm +++ b/src/elm/Form/UserPicker.elm @@ -358,7 +358,7 @@ viewSelectedProfile (Options options) viewConfig { profile, summary } = ) ] ] - [ Icons.trash "" ] + [ Icons.trash "text-red" ] ] diff --git a/src/elm/Icons.elm b/src/elm/Icons.elm index 833f0f7ee..fc62fd6b1 100644 --- a/src/elm/Icons.elm +++ b/src/elm/Icons.elm @@ -309,7 +309,7 @@ question class_ = trash : String -> Svg msg trash class_ = - svg [ width "24", height "24", viewBox "0 0 24 24", class class_ ] [ Svg.path [ fillRule "evenodd", clipRule "evenOdd", d "M20.3077 5.16788H16.3477C15.6644 3.41192 13.9331 2.25 12 2.25C10.0669 2.25 8.33558 3.41192 7.65231 5.16788H3.69231C3.30996 5.16788 3 5.46885 3 5.84013C3 6.2114 3.30996 6.51237 3.69231 6.51237H4.15385V20.4055C4.15385 21.148 4.77376 21.75 5.53846 21.75H18C18.7647 21.75 19.3846 21.148 19.3846 20.4055V6.51237H20.3077C20.69 6.51237 21 6.2114 21 5.84013C21 5.46885 20.69 5.16788 20.3077 5.16788ZM12 3.5993C13.26 3.5993 14.3538 4.04746 14.8938 5.16788H9.10615C9.64615 4.04298 10.74 3.5993 12 3.5993ZM18 20.4055H16.1538V12.7867C16.1538 12.4154 15.8439 12.1144 15.4615 12.1144C15.0792 12.1144 14.7692 12.4154 14.7692 12.7867V20.4055H12.4615V10.0977C12.4615 9.72643 12.1516 9.42545 11.7692 9.42545C11.3869 9.42545 11.0769 9.72643 11.0769 10.0977V20.4055H8.76923V12.7867C8.76923 12.4154 8.45927 12.1144 8.07692 12.1144C7.69457 12.1144 7.38462 12.4154 7.38462 12.7867V20.4055H5.53846V6.51237H18V20.4055Z", fill "#DB1B1B" ] [] ] + svg [ width "24", height "24", viewBox "0 0 24 24", class class_ ] [ Svg.path [ fillRule "evenodd", clipRule "evenOdd", d "M20.3077 5.16788H16.3477C15.6644 3.41192 13.9331 2.25 12 2.25C10.0669 2.25 8.33558 3.41192 7.65231 5.16788H3.69231C3.30996 5.16788 3 5.46885 3 5.84013C3 6.2114 3.30996 6.51237 3.69231 6.51237H4.15385V20.4055C4.15385 21.148 4.77376 21.75 5.53846 21.75H18C18.7647 21.75 19.3846 21.148 19.3846 20.4055V6.51237H20.3077C20.69 6.51237 21 6.2114 21 5.84013C21 5.46885 20.69 5.16788 20.3077 5.16788ZM12 3.5993C13.26 3.5993 14.3538 4.04746 14.8938 5.16788H9.10615C9.64615 4.04298 10.74 3.5993 12 3.5993ZM18 20.4055H16.1538V12.7867C16.1538 12.4154 15.8439 12.1144 15.4615 12.1144C15.0792 12.1144 14.7692 12.4154 14.7692 12.7867V20.4055H12.4615V10.0977C12.4615 9.72643 12.1516 9.42545 11.7692 9.42545C11.3869 9.42545 11.0769 9.72643 11.0769 10.0977V20.4055H8.76923V12.7867C8.76923 12.4154 8.45927 12.1144 8.07692 12.1144C7.69457 12.1144 7.38462 12.4154 7.38462 12.7867V20.4055H5.53846V6.51237H18V20.4055Z", fill "currentColor" ] [] ] edit : String -> Svg msg diff --git a/src/elm/Profile/Contact.elm b/src/elm/Profile/Contact.elm index ef8370934..a1f37fa39 100644 --- a/src/elm/Profile/Contact.elm +++ b/src/elm/Profile/Contact.elm @@ -718,7 +718,7 @@ viewInputWithBackground translators basic = [ onClick (ClickedDeleteContact basic.contactType) , type_ "button" ] - [ Icons.trash "" ] + [ Icons.trash "text-red" ] ] , viewInput translators basic ] From 72398f38f821320bd2d44d047ef079e8cb626118 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 9 Jun 2022 14:05:30 -0300 Subject: [PATCH 015/104] Create wrapper around DeleteStatus --- src/elm/Api/Graphql/DeleteStatus.elm | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/elm/Api/Graphql/DeleteStatus.elm diff --git a/src/elm/Api/Graphql/DeleteStatus.elm b/src/elm/Api/Graphql/DeleteStatus.elm new file mode 100644 index 000000000..efd295e32 --- /dev/null +++ b/src/elm/Api/Graphql/DeleteStatus.elm @@ -0,0 +1,44 @@ +module Api.Graphql.DeleteStatus exposing (DeleteStatus(..), ErrorReason(..), selectionSet) + +import Cambiatus.Object +import Cambiatus.Object.DeleteStatus +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + + +type DeleteStatus + = Deleted + | Error ErrorReason + + +type ErrorReason + = ApiError String + | InvalidStatus + | UnknownError + + +selectionSet : + (SelectionSet DeleteStatus Cambiatus.Object.DeleteStatus + -> SelectionSet (Maybe DeleteStatus) typeLock + ) + -> SelectionSet DeleteStatus typeLock +selectionSet fn = + fn internalSelectionSet + |> SelectionSet.map (Maybe.withDefault (Error UnknownError)) + + +internalSelectionSet : SelectionSet DeleteStatus Cambiatus.Object.DeleteStatus +internalSelectionSet = + SelectionSet.succeed + (\reason status -> + case status of + "success" -> + Deleted + + "error" -> + Error (ApiError reason) + + _ -> + Error InvalidStatus + ) + |> SelectionSet.with Cambiatus.Object.DeleteStatus.reason + |> SelectionSet.with Cambiatus.Object.DeleteStatus.status From 32e76a50ca2ac6f2939b9ce49dfbb8a4825272a8 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 9 Jun 2022 14:08:23 -0300 Subject: [PATCH 016/104] Add ability to delete categories --- .../Community/Settings/Shop/Categories.elm | 175 +++++++++++++++++- src/elm/Shop/Category.elm | 18 +- 2 files changed, 178 insertions(+), 15 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index a051216aa..c7ce88560 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -8,13 +8,14 @@ module Page.Community.Settings.Shop.Categories exposing , view ) +import Api.Graphql.DeleteStatus import Community import EverySet exposing (EverySet) import Form import Form.Text import Form.Validate import Graphql.Http -import Html exposing (Html, button, details, div, li, span, summary, text, ul) +import Html exposing (Html, button, details, div, li, p, span, summary, text, ul) import Html.Attributes exposing (class, classList) import Html.Events exposing (onClick) import Icons @@ -27,6 +28,7 @@ import Shop.Category import Slug exposing (Slug) import Translation import Tree +import Tree.Zipper import UpdateResult as UR import Utils import View.Components @@ -42,6 +44,7 @@ type alias Model = { expandedCategories : EverySet Shop.Category.Id , newCategoryState : NewCategoryState , categoryModalState : CategoryModalState + , categoryDeletionState : CategoryDeletionState } @@ -53,6 +56,7 @@ init _ = -- TODO - Should we allow multiple editors to be open at once? , newCategoryState = NotEditing , categoryModalState = Closed + , categoryDeletionState = NotDeleting } |> UR.init |> UR.addExt (LoggedIn.RequestedReloadCommunityField Community.ShopCategoriesField) @@ -75,6 +79,12 @@ type CategoryModalState | Open Shop.Category.Id (Form.Model UpdateCategoryFormInput) +type CategoryDeletionState + = NotDeleting + | AskingForConfirmation Shop.Category.Id + | Deleting Shop.Category.Id + + type Msg = NoOp | ClickedToggleExpandCategory Shop.Category.Id @@ -88,6 +98,10 @@ type Msg | GotUpdateCategoryFormMsg (Form.Msg UpdateCategoryFormInput) | SubmittedUpdateCategoryForm UpdateCategoryFormOutput | FinishedUpdatingCategory (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Model)) (Maybe Shop.Category.Model)) + | ClickedDeleteCategory Shop.Category.Id + | ClosedConfirmDeleteModal + | ConfirmedDeleteCategory Shop.Category.Id + | CompletedDeletingCategory (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) type alias UpdateResult = @@ -101,11 +115,11 @@ type alias UpdateResult = update : Msg -> Model -> LoggedIn.Model -> UpdateResult update msg model loggedIn = let - getCategory : Shop.Category.Id -> Maybe Shop.Category.Model - getCategory categoryId = + getCategoryZipper : Shop.Category.Id -> Maybe (Tree.Zipper.Zipper Shop.Category.Model) + getCategoryZipper categoryId = case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.Success ( _, categories ) -> - findInTrees (\category -> category.id == categoryId) categories + findInForest (\category -> category.id == categoryId) categories _ -> Nothing @@ -144,7 +158,7 @@ update msg model loggedIn = |> UR.init ClickedCategory categoryId -> - case getCategory categoryId of + case getCategoryZipper categoryId |> Maybe.map Tree.Zipper.label of Just category -> { model | categoryModalState = @@ -212,6 +226,7 @@ update msg model loggedIn = , slug = slug , parentId = parentId } + Shop.Category.selectionSet ) FinishedCreatingCategory ) @@ -246,11 +261,11 @@ update msg model loggedIn = model SubmittedUpdateCategoryForm formOutput -> - case getCategory formOutput.id of + case getCategoryZipper formOutput.id of Nothing -> UR.init model - Just category -> + Just zipper -> { model | categoryModalState = case model.categoryModalState of @@ -263,11 +278,13 @@ update msg model loggedIn = |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn - (Shop.Category.update category + (Shop.Category.update (Tree.Zipper.label zipper) + -- TODO - Include children { name = formOutput.name , slug = formOutput.slug , description = formOutput.description } + Shop.Category.selectionSet ) FinishedUpdatingCategory ) @@ -287,6 +304,73 @@ update msg model loggedIn = FinishedUpdatingCategory _ -> UR.init model + ClickedDeleteCategory categoryId -> + case getCategoryZipper categoryId of + Nothing -> + model + |> UR.init + + Just zipper -> + if List.isEmpty (Tree.Zipper.children zipper) then + { model | categoryDeletionState = Deleting categoryId } + |> UR.init + |> UR.addExt + (LoggedIn.mutation + loggedIn + (Api.Graphql.DeleteStatus.selectionSet + (Shop.Category.delete categoryId) + ) + CompletedDeletingCategory + ) + + else + { model | categoryDeletionState = AskingForConfirmation categoryId } + |> UR.init + + ClosedConfirmDeleteModal -> + { model | categoryDeletionState = NotDeleting } + |> UR.init + + ConfirmedDeleteCategory categoryId -> + { model | categoryDeletionState = Deleting categoryId } + |> UR.init + |> UR.addExt + (LoggedIn.mutation + loggedIn + (Api.Graphql.DeleteStatus.selectionSet + (Shop.Category.delete categoryId) + ) + CompletedDeletingCategory + ) + + CompletedDeletingCategory (RemoteData.Success Api.Graphql.DeleteStatus.Deleted) -> + { model | categoryDeletionState = NotDeleting } + |> UR.init + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Deleted") + + CompletedDeletingCategory (RemoteData.Success (Api.Graphql.DeleteStatus.Error _)) -> + { model | categoryDeletionState = NotDeleting } + |> UR.init + -- TODO - I18N + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "error :(") + + CompletedDeletingCategory (RemoteData.Failure err) -> + { model | categoryDeletionState = NotDeleting } + |> UR.init + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (loggedIn.shared.translators.t "error.unknown")) + |> UR.logGraphqlError msg + (Just loggedIn.accountName) + "Got an error when trying to delete category" + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [] + err + + CompletedDeletingCategory _ -> + model + |> UR.init + -- VIEW @@ -349,6 +433,15 @@ view_ translators model categories = Just openCategory -> viewCategoryModal translators openCategory formModel + , case model.categoryDeletionState of + NotDeleting -> + text "" + + AskingForConfirmation categoryId -> + viewConfirmDeleteCategoryModal categoryId + + Deleting categoryId -> + viewConfirmDeleteCategoryModal categoryId ] @@ -390,7 +483,7 @@ viewCategoryWithChildren translators model category children = , class "parent" ] [ summary - [ class "marker-hidden flex items-center rounded-sm parent-hover:bg-blue-600/20" + [ class "marker-hidden flex items-center rounded-sm parent-hover:bg-blue-600/10" , Html.Events.preventDefaultOn "click" (Json.Decode.succeed ( NoOp, True )) ] @@ -401,6 +494,11 @@ viewCategoryWithChildren translators model category children = [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) , viewCategory category ] + , button + [ class "h-8 group mr-2" + , onClick (ClickedDeleteCategory category.id) + ] + [ Icons.trash "h-4 text-black group-hover:text-red" ] ] , div [ class "ml-4 flex flex-col mb-4 mt-2" ] [ ul @@ -422,7 +520,7 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton customAttrs = button - (class "flex items-center px-2 h-8 font-bold hover:bg-blue-600/20 rounded-sm" + (class "flex items-center px-2 h-8 font-bold hover:bg-blue-600/10 rounded-sm" :: onClick (ClickedAddCategory parentId) :: customAttrs ) @@ -512,6 +610,40 @@ viewCategoryModal translators category formModel = |> Modal.toHtml +viewConfirmDeleteCategoryModal : Shop.Category.Id -> Html Msg +viewConfirmDeleteCategoryModal categoryId = + Modal.initWith + { isVisible = True + , closeMsg = ClosedConfirmDeleteModal + } + -- TODO - I18N + |> Modal.withHeader "Delete category" + |> Modal.withBody + -- TODO - I18N + [ p [] [ text "If you delete this category, all of its sub-categories will also be permanently deleted." ] + + -- TODO - I18N + , p [] [ text "Are you sure you want to delete this category?" ] + ] + |> Modal.withFooter + [ div [ class "flex flex-col sm:flex-row gap-4" ] + [ button + [ class "button button-secondary w-full sm:w-40" + , onClick ClosedConfirmDeleteModal + ] + -- TODO - I18N + [ text "Cancel" ] + , button + [ class "button button-danger w-full sm:w-40" + , onClick (ConfirmedDeleteCategory categoryId) + ] + -- TODO - I18N + [ text "Delete" ] + ] + ] + |> Modal.toHtml + + -- FORM @@ -693,6 +825,17 @@ findInTrees fn trees = |> List.Extra.find fn +findInForest : (a -> Bool) -> List (Tree.Tree a) -> Maybe (Tree.Zipper.Zipper a) +findInForest fn trees = + case trees of + [] -> + Nothing + + firstTree :: otherTrees -> + Tree.Zipper.fromForest firstTree otherTrees + |> Tree.Zipper.findFromRoot fn + + msgToString : Msg -> List String msgToString msg = case msg of @@ -731,3 +874,15 @@ msgToString msg = FinishedUpdatingCategory r -> [ "FinishedUpdatingCategory", UR.remoteDataToString r ] + + ClickedDeleteCategory _ -> + [ "ClickedDeleteCategory" ] + + ClosedConfirmDeleteModal -> + [ "ClosedConfirmDeleteModal" ] + + ConfirmedDeleteCategory _ -> + [ "ConfirmedDeleteCategory" ] + + CompletedDeletingCategory r -> + [ "CompletedDeletingCategory", UR.remoteDataToString r ] diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 8761e672d..641a41e5a 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, treesSelectionSet, update) +module Shop.Category exposing (Id, Model, Tree, create, delete, selectionSet, treesSelectionSet, update) import Cambiatus.Mutation import Cambiatus.Object @@ -33,7 +33,8 @@ create : , slug : Slug , parentId : Maybe Id } - -> SelectionSet (Maybe Model) RootMutation + -> SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe decodesTo) RootMutation create { name, slug, description, parentId } = Cambiatus.Mutation.category (\_ -> @@ -54,13 +55,13 @@ create { name, slug, description, parentId } = , description = description , categories = [] } - selectionSet update : Model -> { name : String, description : String, slug : Slug } - -> SelectionSet (Maybe Model) RootMutation + -> SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe decodesTo) RootMutation update model { name, description, slug } = let unwrapId : Id -> Int @@ -86,7 +87,14 @@ update model { name, description, slug } = , description = description , categories = [] } - selectionSet + + +delete : + Id + -> SelectionSet decodesTo Cambiatus.Object.DeleteStatus + -> SelectionSet (Maybe decodesTo) RootMutation +delete (Id id) = + Cambiatus.Mutation.deleteCategory { id = id } selectionSet : SelectionSet Model Cambiatus.Object.Category From 077468ab63a85f4ab280911d6790fabfe6026496 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 9 Jun 2022 16:13:13 -0300 Subject: [PATCH 017/104] Improve errors, update community model after completing operations --- src/elm/Api/Graphql/DeleteStatus.elm | 15 +- src/elm/Log.elm | 17 ++ .../Community/Settings/Shop/Categories.elm | 169 +++++++++++++++--- src/elm/Shop/Category.elm | 8 +- src/elm/UpdateResult.elm | 14 +- 5 files changed, 198 insertions(+), 25 deletions(-) diff --git a/src/elm/Api/Graphql/DeleteStatus.elm b/src/elm/Api/Graphql/DeleteStatus.elm index efd295e32..515b00112 100644 --- a/src/elm/Api/Graphql/DeleteStatus.elm +++ b/src/elm/Api/Graphql/DeleteStatus.elm @@ -1,4 +1,4 @@ -module Api.Graphql.DeleteStatus exposing (DeleteStatus(..), ErrorReason(..), selectionSet) +module Api.Graphql.DeleteStatus exposing (DeleteStatus(..), ErrorReason(..), reasonToErrorString, selectionSet) import Cambiatus.Object import Cambiatus.Object.DeleteStatus @@ -42,3 +42,16 @@ internalSelectionSet = ) |> SelectionSet.with Cambiatus.Object.DeleteStatus.reason |> SelectionSet.with Cambiatus.Object.DeleteStatus.status + + +reasonToErrorString : ErrorReason -> String +reasonToErrorString reason = + case reason of + ApiError apiMessage -> + "Error returned from API: " ++ apiMessage + + InvalidStatus -> + "Invalid status (`status` field was not `success` or `error`)" + + UnknownError -> + "UnknownError - API returned a null object instead of a DeleteStatus object" diff --git a/src/elm/Log.elm b/src/elm/Log.elm index 2d4d426ee..69e6d3abe 100755 --- a/src/elm/Log.elm +++ b/src/elm/Log.elm @@ -11,6 +11,7 @@ port module Log exposing , addBreadcrumb , contextFromCommunity , fromDecodeError + , fromDeletionStatusError , fromGraphqlHttpError , fromHttpError , fromImpossible @@ -37,6 +38,7 @@ can see details about the error and analyze further. -} +import Api.Graphql.DeleteStatus import Dict exposing (Dict) import Eos import Eos.Account @@ -368,6 +370,21 @@ fromIncompatibleMsg transaction maybeUser location contexts = } +{-| Creates an event out of an error when trying to delete something with the +GraphQL API +-} +fromDeletionStatusError : msg -> Maybe Eos.Account.Name -> Location -> List Context -> Api.Graphql.DeleteStatus.ErrorReason -> Event msg +fromDeletionStatusError transaction maybeUser location contexts reason = + { username = maybeUser + , message = Api.Graphql.DeleteStatus.reasonToErrorString reason + , tags = [ TypeTag GraphqlErrorType ] + , location = location + , contexts = contexts + , transaction = transaction + , level = Error + } + + {-| Creates a Context out of a Community. It's an extendable record because: 1. Import cycles diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index c7ce88560..c4996e227 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -10,6 +10,7 @@ module Page.Community.Settings.Shop.Categories exposing import Api.Graphql.DeleteStatus import Community +import Dict import EverySet exposing (EverySet) import Form import Form.Text @@ -101,7 +102,7 @@ type Msg | ClickedDeleteCategory Shop.Category.Id | ClosedConfirmDeleteModal | ConfirmedDeleteCategory Shop.Category.Id - | CompletedDeletingCategory (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) + | CompletedDeletingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) type alias UpdateResult = @@ -231,16 +232,74 @@ update msg model loggedIn = FinishedCreatingCategory ) - FinishedCreatingCategory (RemoteData.Success _) -> + FinishedCreatingCategory (RemoteData.Success (Just category)) -> -- TODO - Should we open the form to create another category? + let + insertInForest : List Shop.Category.Tree -> List Shop.Category.Tree + insertInForest forest = + case category.parentId of + Nothing -> + forest ++ [ Tree.singleton category ] + + Just parentId -> + case findInForest (\parent -> parent.id == parentId) forest of + Nothing -> + forest ++ [ Tree.singleton category ] + + Just zipper -> + zipper + |> Tree.Zipper.mapTree (Tree.appendChild (Tree.singleton category)) + |> Tree.Zipper.toForest + |> (\( first, others ) -> first :: others) + + insertInCommunity : UpdateResult -> UpdateResult + insertInCommunity = + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + insertInForest categories + |> Community.ShopCategories + |> LoggedIn.SetCommunityField + |> UR.addExt + + _ -> + identity + in { model | newCategoryState = NotEditing } |> UR.init - -- TODO - Remove this once we update the UI automatically - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Yay!") + |> insertInCommunity + + FinishedCreatingCategory (RemoteData.Success Nothing) -> + { model + | newCategoryState = + case model.newCategoryState of + NotEditing -> + NotEditing + + EditingNewCategory newCategoryData -> + { newCategoryData | form = Form.withDisabled False newCategoryData.form } + |> EditingNewCategory + } + |> UR.init + -- TODO - I18N + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.logImpossible msg + "Got Nothing after creating category" + (Just loggedIn.accountName) + { moduleName = "Page.Community.Settings.Shop.Categories", function = "update" } + [] FinishedCreatingCategory (RemoteData.Failure _) -> - -- TODO - re-enable form - UR.init model + { model + | newCategoryState = + case model.newCategoryState of + NotEditing -> + NotEditing + + EditingNewCategory newCategoryData -> + { newCategoryData | form = Form.withDisabled False newCategoryData.form } + |> EditingNewCategory + } + |> UR.init -- TODO - I18N |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") @@ -289,17 +348,51 @@ update msg model loggedIn = FinishedUpdatingCategory ) - FinishedUpdatingCategory (RemoteData.Success _) -> + FinishedUpdatingCategory (RemoteData.Success (Just category)) -> + let + updateInCommunity : UpdateResult -> UpdateResult + updateInCommunity = + case getCategoryZipper category.id of + Nothing -> + identity + + Just zipper -> + zipper + |> Tree.Zipper.replaceLabel category + |> Tree.Zipper.toForest + |> (\( first, others ) -> first :: others) + |> Community.ShopCategories + |> LoggedIn.SetCommunityField + |> UR.addExt + in { model | categoryModalState = Closed } |> UR.init - -- TODO - Remove this once we update the UI automatically - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Yay!") + |> updateInCommunity - FinishedUpdatingCategory (RemoteData.Failure _) -> - -- TODO - re-enable form - UR.init model + FinishedUpdatingCategory (RemoteData.Success Nothing) -> + { model | categoryModalState = Closed } + |> UR.init + -- TODO - I18N + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.logImpossible msg + "Got Nothing after updating category" + (Just loggedIn.accountName) + { moduleName = "Page.Community.Settings.Shop.Categories", function = "update" } + [] + + FinishedUpdatingCategory (RemoteData.Failure err) -> + { model | categoryModalState = Closed } + |> UR.init -- TODO - I18N |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.logGraphqlError msg + (Just loggedIn.accountName) + "Got an error when updating category" + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [] + err FinishedUpdatingCategory _ -> UR.init model @@ -320,7 +413,7 @@ update msg model loggedIn = (Api.Graphql.DeleteStatus.selectionSet (Shop.Category.delete categoryId) ) - CompletedDeletingCategory + (CompletedDeletingCategory categoryId) ) else @@ -340,21 +433,52 @@ update msg model loggedIn = (Api.Graphql.DeleteStatus.selectionSet (Shop.Category.delete categoryId) ) - CompletedDeletingCategory + (CompletedDeletingCategory categoryId) ) - CompletedDeletingCategory (RemoteData.Success Api.Graphql.DeleteStatus.Deleted) -> + CompletedDeletingCategory categoryId (RemoteData.Success Api.Graphql.DeleteStatus.Deleted) -> + let + removeFromForest : Tree.Zipper.Zipper Shop.Category.Model -> List Shop.Category.Tree + removeFromForest zipper = + zipper + |> Tree.Zipper.removeTree + |> Maybe.map (Tree.Zipper.toForest >> (\( first, others ) -> first :: others)) + |> Maybe.withDefault [] + + removeFromCommunity : UpdateResult -> UpdateResult + removeFromCommunity = + case getCategoryZipper categoryId of + Nothing -> + identity + + Just zipper -> + zipper + |> removeFromForest + |> Community.ShopCategories + |> LoggedIn.SetCommunityField + |> UR.addExt + in { model | categoryDeletionState = NotDeleting } |> UR.init - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Success "Deleted") + |> removeFromCommunity - CompletedDeletingCategory (RemoteData.Success (Api.Graphql.DeleteStatus.Error _)) -> + CompletedDeletingCategory categoryId (RemoteData.Success (Api.Graphql.DeleteStatus.Error reason)) -> { model | categoryDeletionState = NotDeleting } |> UR.init -- TODO - I18N |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "error :(") + |> UR.logDeletionStatusError msg + (Just loggedIn.accountName) + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [ { name = "Category" + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + } + ] + reason - CompletedDeletingCategory (RemoteData.Failure err) -> + CompletedDeletingCategory categoryId (RemoteData.Failure err) -> { model | categoryDeletionState = NotDeleting } |> UR.init |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (loggedIn.shared.translators.t "error.unknown")) @@ -364,10 +488,13 @@ update msg model loggedIn = { moduleName = "Page.Community.Settings.Shop.Categories" , function = "update" } - [] + [ { name = "Category" + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + } + ] err - CompletedDeletingCategory _ -> + CompletedDeletingCategory _ _ -> model |> UR.init @@ -884,5 +1011,5 @@ msgToString msg = ConfirmedDeleteCategory _ -> [ "ConfirmedDeleteCategory" ] - CompletedDeletingCategory r -> + CompletedDeletingCategory _ r -> [ "CompletedDeletingCategory", UR.remoteDataToString r ] diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 641a41e5a..3c5ed0051 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, delete, selectionSet, treesSelectionSet, update) +module Shop.Category exposing (Id, Model, Tree, create, delete, encodeId, selectionSet, treesSelectionSet, update) import Cambiatus.Mutation import Cambiatus.Object @@ -6,6 +6,7 @@ import Cambiatus.Object.Category import Graphql.Operation exposing (RootMutation) import Graphql.OptionalArgument as OptionalArgument import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) +import Json.Encode import Maybe.Extra import Slug exposing (Slug) import Tree @@ -166,3 +167,8 @@ type Id idSelectionSet : SelectionSet Id Cambiatus.Object.Category idSelectionSet = SelectionSet.map Id Cambiatus.Object.Category.id + + +encodeId : Id -> Json.Encode.Value +encodeId (Id id) = + Json.Encode.int id diff --git a/src/elm/UpdateResult.elm b/src/elm/UpdateResult.elm index 79ff5af6c..713052e9e 100755 --- a/src/elm/UpdateResult.elm +++ b/src/elm/UpdateResult.elm @@ -1,6 +1,6 @@ module UpdateResult exposing ( UpdateResult - , init, addCmd, addMsg, addExt, addPort, logHttpError, logImpossible, logGraphqlError, toModelCmd + , init, addCmd, addMsg, addExt, addPort, logHttpError, logImpossible, logGraphqlError, logDeletionStatusError, toModelCmd , fromChild , map, mapModel, setModel , addBreadcrumb, logDecodingError, logEvent, logIncompatibleMsg, logJsonValue, remoteDataToString, resultToString @@ -19,7 +19,7 @@ In a scenario such as displaying a request to sign a transfer or a sale is where # Common Helpers -@docs init, addCmd, addMsg, addExt, addPort, logHttpError, logImpossible, logGraphqlError, toModelCmd +@docs init, addCmd, addMsg, addExt, addPort, logHttpError, logImpossible, logGraphqlError, logDeletionStatusError, toModelCmd # Using components @@ -33,6 +33,7 @@ In a scenario such as displaying a request to sign a transfer or a sale is where -} +import Api.Graphql.DeleteStatus import Eos.Account import Graphql.Http import Http @@ -340,6 +341,15 @@ logIncompatibleMsg transaction maybeUser location contexts = |> logEvent +{-| Send an Event to Sentry so we can debug later. Should be used when deleting +something with the GraphQL API, but getting an error from Api.Graphql.DeleteStatus. +-} +logDeletionStatusError : msg -> Maybe Eos.Account.Name -> Log.Location -> List Log.Context -> Api.Graphql.DeleteStatus.ErrorReason -> UpdateResult m msg eMsg -> UpdateResult m msg eMsg +logDeletionStatusError transaction maybeUser location contexts reason = + Log.fromDeletionStatusError transaction maybeUser location contexts reason + |> logEvent + + -- EXTERNAL HELPERS From b88c4407be18ed5009f2e64263519be06761fb89 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 09:51:02 -0300 Subject: [PATCH 018/104] Leave form open after creating new category --- .../Page/Community/Settings/Shop/Categories.elm | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index c4996e227..307cd8ed0 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -17,7 +17,7 @@ import Form.Text import Form.Validate import Graphql.Http import Html exposing (Html, button, details, div, li, p, span, summary, text, ul) -import Html.Attributes exposing (class, classList) +import Html.Attributes exposing (class, classList, type_) import Html.Events exposing (onClick) import Icons import Json.Decode @@ -53,8 +53,6 @@ init : LoggedIn.Model -> UpdateResult init _ = -- TODO - Should we start them all expanded? { expandedCategories = EverySet.empty - - -- TODO - Should we allow multiple editors to be open at once? , newCategoryState = NotEditing , categoryModalState = Closed , categoryDeletionState = NotDeleting @@ -233,7 +231,6 @@ update msg model loggedIn = ) FinishedCreatingCategory (RemoteData.Success (Just category)) -> - -- TODO - Should we open the form to create another category? let insertInForest : List Shop.Category.Tree -> List Shop.Category.Tree insertInForest forest = @@ -264,7 +261,13 @@ update msg model loggedIn = _ -> identity in - { model | newCategoryState = NotEditing } + { model + | newCategoryState = + EditingNewCategory + { parent = category.parentId + , form = Form.init { name = "", description = "" } + } + } |> UR.init |> insertInCommunity @@ -674,6 +677,7 @@ viewAddCategory translators attrs model maybeParentCategory = [ div [ class "flex flex-col sm:flex-row justify-end gap-4" ] [ button [ class "button button-secondary w-full sm:w-40" + , type_ "button" , onClick ClickedCancelAddCategory ] -- TODO - I18N @@ -743,7 +747,7 @@ viewConfirmDeleteCategoryModal categoryId = { isVisible = True , closeMsg = ClosedConfirmDeleteModal } - -- TODO - I18N + -- TODO - I18N - Include category name |> Modal.withHeader "Delete category" |> Modal.withBody -- TODO - I18N From a4284a0336705c9426e9154cb3ab6802ca4a4c04 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 10:19:38 -0300 Subject: [PATCH 019/104] Rework deletion status to allow multiple simultaneous deletions, add back button --- .../Community/Settings/Shop/Categories.elm | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 307cd8ed0..2e4aa223f 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -45,7 +45,8 @@ type alias Model = { expandedCategories : EverySet Shop.Category.Id , newCategoryState : NewCategoryState , categoryModalState : CategoryModalState - , categoryDeletionState : CategoryDeletionState + , askingForDeleteConfirmation : Maybe Shop.Category.Id + , deleting : EverySet Shop.Category.Id } @@ -55,7 +56,8 @@ init _ = { expandedCategories = EverySet.empty , newCategoryState = NotEditing , categoryModalState = Closed - , categoryDeletionState = NotDeleting + , askingForDeleteConfirmation = Nothing + , deleting = EverySet.empty } |> UR.init |> UR.addExt (LoggedIn.RequestedReloadCommunityField Community.ShopCategoriesField) @@ -78,12 +80,6 @@ type CategoryModalState | Open Shop.Category.Id (Form.Model UpdateCategoryFormInput) -type CategoryDeletionState - = NotDeleting - | AskingForConfirmation Shop.Category.Id - | Deleting Shop.Category.Id - - type Msg = NoOp | ClickedToggleExpandCategory Shop.Category.Id @@ -408,7 +404,7 @@ update msg model loggedIn = Just zipper -> if List.isEmpty (Tree.Zipper.children zipper) then - { model | categoryDeletionState = Deleting categoryId } + { model | deleting = EverySet.insert categoryId model.deleting } |> UR.init |> UR.addExt (LoggedIn.mutation @@ -420,15 +416,18 @@ update msg model loggedIn = ) else - { model | categoryDeletionState = AskingForConfirmation categoryId } + { model | askingForDeleteConfirmation = Just categoryId } |> UR.init ClosedConfirmDeleteModal -> - { model | categoryDeletionState = NotDeleting } + { model | askingForDeleteConfirmation = Nothing } |> UR.init ConfirmedDeleteCategory categoryId -> - { model | categoryDeletionState = Deleting categoryId } + { model + | deleting = EverySet.insert categoryId model.deleting + , askingForDeleteConfirmation = Nothing + } |> UR.init |> UR.addExt (LoggedIn.mutation @@ -461,12 +460,12 @@ update msg model loggedIn = |> LoggedIn.SetCommunityField |> UR.addExt in - { model | categoryDeletionState = NotDeleting } + { model | deleting = EverySet.remove categoryId model.deleting } |> UR.init |> removeFromCommunity CompletedDeletingCategory categoryId (RemoteData.Success (Api.Graphql.DeleteStatus.Error reason)) -> - { model | categoryDeletionState = NotDeleting } + { model | deleting = EverySet.remove categoryId model.deleting } |> UR.init -- TODO - I18N |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "error :(") @@ -482,7 +481,7 @@ update msg model loggedIn = reason CompletedDeletingCategory categoryId (RemoteData.Failure err) -> - { model | categoryDeletionState = NotDeleting } + { model | deleting = EverySet.remove categoryId model.deleting } |> UR.init |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (loggedIn.shared.translators.t "error.unknown")) |> UR.logGraphqlError msg @@ -514,26 +513,27 @@ view loggedIn model = "TODO" in { title = title - - -- TODO - Add back arrow , content = - case Community.getField loggedIn.selectedCommunity .shopCategories of - RemoteData.NotAsked -> - Page.fullPageLoading loggedIn.shared + div [] + [ Page.viewHeader loggedIn title + , case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.NotAsked -> + Page.fullPageLoading loggedIn.shared - RemoteData.Loading -> - Page.fullPageLoading loggedIn.shared + RemoteData.Loading -> + Page.fullPageLoading loggedIn.shared - RemoteData.Failure fieldErr -> - case fieldErr of - Community.CommunityError err -> - Page.fullPageGraphQLError title err + RemoteData.Failure fieldErr -> + case fieldErr of + Community.CommunityError err -> + Page.fullPageGraphQLError title err - Community.FieldError err -> - Page.fullPageGraphQLError title err + Community.FieldError err -> + Page.fullPageGraphQLError title err - RemoteData.Success ( _, categories ) -> - view_ loggedIn.shared.translators model categories + RemoteData.Success ( _, categories ) -> + view_ loggedIn.shared.translators model categories + ] } @@ -563,14 +563,11 @@ view_ translators model categories = Just openCategory -> viewCategoryModal translators openCategory formModel - , case model.categoryDeletionState of - NotDeleting -> + , case model.askingForDeleteConfirmation of + Nothing -> text "" - AskingForConfirmation categoryId -> - viewConfirmDeleteCategoryModal categoryId - - Deleting categoryId -> + Just categoryId -> viewConfirmDeleteCategoryModal categoryId ] @@ -603,7 +600,9 @@ viewCategoryWithChildren translators model category children = else "-rotate-90" in - li [] + li + [ classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) ] + ] [ details [ if isOpen then Html.Attributes.attribute "open" "true" @@ -611,6 +610,7 @@ viewCategoryWithChildren translators model category children = else class "" , class "parent" + , classList [ ( "pointer-events-none", EverySet.member category.id model.deleting ) ] ] [ summary [ class "marker-hidden flex items-center rounded-sm parent-hover:bg-blue-600/10" From 091961a4e21620f1b7cf4265fe7e607e2a7bded1 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 10:28:59 -0300 Subject: [PATCH 020/104] Restrict page to admins --- src/elm/Page/Community/Settings/Shop/Categories.elm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 2e4aa223f..928752b6a 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -531,8 +531,12 @@ view loggedIn model = Community.FieldError err -> Page.fullPageGraphQLError title err - RemoteData.Success ( _, categories ) -> - view_ loggedIn.shared.translators model categories + RemoteData.Success ( community, categories ) -> + if community.creator == loggedIn.accountName then + view_ loggedIn.shared.translators model categories + + else + Page.fullPageNotFound (loggedIn.shared.translators.t "community.edit.unauthorized") "" ] } From 4106a578ee83031435a550311d5a55dd502c034c Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 10:33:46 -0300 Subject: [PATCH 021/104] Restructure html to have more semantic lists --- src/elm/Page/Community/Settings/Shop/Categories.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 928752b6a..6aa8aa4cd 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -604,7 +604,7 @@ viewCategoryWithChildren translators model category children = else "-rotate-90" in - li + div [ classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) ] ] [ details @@ -639,7 +639,7 @@ viewCategoryWithChildren translators model category children = [ class "grid gap-y-2" , classList [ ( "mb-2", not (List.isEmpty children) ) ] ] - children + (List.map (\child -> li [] [ child ]) children) , viewAddCategory translators [] model (Just category) ] ] From d3c24187189075f4aa9fca6c6cfd3bf8eb179582 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 11:09:35 -0300 Subject: [PATCH 022/104] Leave summaries highlighted while category creation form is open, transition colors --- .../Community/Settings/Shop/Categories.elm | 70 +++++++++++++++++-- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 6aa8aa4cd..613f67227 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -577,8 +577,19 @@ view_ translators model categories = viewCategoryTree : Translation.Translators -> Model -> Shop.Category.Tree -> Html Msg -viewCategoryTree translators model = - Tree.restructure identity (viewCategoryWithChildren translators model) +viewCategoryTree translators model rootTree = + let + rootZipper = + Tree.Zipper.fromTree rootTree + in + Tree.restructure + (\category -> + rootZipper + |> Tree.Zipper.findFromRoot (\{ id } -> id == category.id) + |> Maybe.withDefault (Tree.singleton category |> Tree.Zipper.fromTree) + ) + (viewCategoryWithChildren translators model) + rootTree viewCategory : Shop.Category.Model -> Html Msg @@ -591,9 +602,12 @@ viewCategory category = ] -viewCategoryWithChildren : Translation.Translators -> Model -> Shop.Category.Model -> List (Html Msg) -> Html Msg -viewCategoryWithChildren translators model category children = +viewCategoryWithChildren : Translation.Translators -> Model -> Tree.Zipper.Zipper Shop.Category.Model -> List (Html Msg) -> Html Msg +viewCategoryWithChildren translators model zipper children = let + category = + Tree.Zipper.label zipper + isOpen = EverySet.member category.id model.expandedCategories @@ -603,9 +617,25 @@ viewCategoryWithChildren translators model category children = else "-rotate-90" + + isParentOfNewCategoryForm = + case model.newCategoryState of + NotEditing -> + False + + EditingNewCategory { parent } -> + case parent of + Nothing -> + False + + Just parentId -> + isAncestorOf (\childId { id } -> childId == id) + parentId + zipper in div - [ classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) ] + [ class "transition-colors" + , classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) ] ] [ details [ if isOpen then @@ -617,7 +647,11 @@ viewCategoryWithChildren translators model category children = , classList [ ( "pointer-events-none", EverySet.member category.id model.deleting ) ] ] [ summary - [ class "marker-hidden flex items-center rounded-sm parent-hover:bg-blue-600/10" + [ class "marker-hidden flex items-center rounded-sm transition-colors" + , classList + [ ( "!bg-green/20", isParentOfNewCategoryForm ) + , ( "parent-hover:bg-blue-600/10", not isParentOfNewCategoryForm ) + ] , Html.Events.preventDefaultOn "click" (Json.Decode.succeed ( NoOp, True )) ] @@ -654,7 +688,7 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton customAttrs = button - (class "flex items-center px-2 h-8 font-bold hover:bg-blue-600/10 rounded-sm" + (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-blue-600/10 rounded-sm" :: onClick (ClickedAddCategory parentId) :: customAttrs ) @@ -971,6 +1005,28 @@ findInForest fn trees = |> Tree.Zipper.findFromRoot fn +isAncestorOf : (child -> a -> Bool) -> child -> Tree.Zipper.Zipper a -> Bool +isAncestorOf equals child parentZipper = + let + isDirectParent = + equals child (Tree.Zipper.label parentZipper) + + isIndirectAncestor = + Tree.Zipper.children parentZipper + |> List.any + (\childTree -> + if equals child (Tree.label childTree) then + True + + else + isAncestorOf equals + child + (Tree.Zipper.fromTree childTree) + ) + in + isDirectParent || isIndirectAncestor + + msgToString : Msg -> List String msgToString msg = case msg of From 8d11bfb689183d56d73a9ab7f7322cfeee5dbe15 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 17:14:21 -0300 Subject: [PATCH 023/104] Use markdown for category description --- .../Community/Settings/Shop/Categories.elm | 40 +++++++++---------- src/elm/Shop/Category.elm | 16 ++++---- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 613f67227..7d6cb3eb1 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -13,6 +13,7 @@ import Community import Dict import EverySet exposing (EverySet) import Form +import Form.RichText import Form.Text import Form.Validate import Graphql.Http @@ -22,6 +23,7 @@ import Html.Events exposing (onClick) import Icons import Json.Decode import List.Extra +import Markdown exposing (Markdown) import Page import RemoteData exposing (RemoteData) import Session.LoggedIn as LoggedIn @@ -142,7 +144,7 @@ update msg model loggedIn = , form = Form.init { name = "" - , description = "" + , description = Form.RichText.initModel "new-description-input" Nothing } } } @@ -160,7 +162,7 @@ update msg model loggedIn = Open categoryId (Form.init { name = category.name - , description = category.description + , description = Form.RichText.initModel "update-category-description" (Just category.description) } ) } @@ -261,7 +263,7 @@ update msg model loggedIn = | newCategoryState = EditingNewCategory { parent = category.parentId - , form = Form.init { name = "", description = "" } + , form = Form.init { name = "", description = Form.RichText.initModel "new-description-input" Nothing } } } |> UR.init @@ -819,14 +821,14 @@ viewConfirmDeleteCategoryModal categoryId = type alias NewCategoryFormInput = { name : String - , description : String + , description : Form.RichText.Model } type alias NewCategoryFormOutput = { name : String , slug : Slug - , description : String + , description : Markdown } @@ -838,15 +840,12 @@ newCategoryForm translators = ) |> Form.with (nameAndSlugForm translators { nameFieldId = "new-category-name" }) |> Form.with - (Form.Text.init - -- TODO - I18N - { label = "Description" - , id = "new-category-description" - } - |> Form.textField + -- TODO - I18N + (Form.RichText.init { label = "Description" } + |> Form.richText { parser = Form.Validate.succeed - >> Form.Validate.stringLongerThan 3 + >> Form.Validate.markdownLongerThan 3 >> Form.Validate.validate translators , value = .description , update = \newDescription values -> { values | description = newDescription } @@ -857,7 +856,7 @@ newCategoryForm translators = type alias UpdateCategoryFormInput = { name : String - , description : String + , description : Form.RichText.Model } @@ -865,7 +864,7 @@ type alias UpdateCategoryFormOutput = { id : Shop.Category.Id , name : String , slug : Slug - , description : String + , description : Markdown } @@ -881,16 +880,13 @@ updateCategoryForm translators id = ) |> Form.with (nameAndSlugForm translators { nameFieldId = "update-category-name" }) |> Form.with - (Form.Text.init - -- TODO - I18N - { label = "Description" - , id = "update-category-description" - } - |> Form.Text.withContainerAttrs [ class "mb-0" ] - |> Form.textField + -- TODO - I18N + (Form.RichText.init { label = "Description" } + |> Form.RichText.withContainerAttrs [ class "mb-0" ] + |> Form.richText { parser = Form.Validate.succeed - >> Form.Validate.stringLongerThan 3 + >> Form.Validate.markdownLongerThan 3 >> Form.Validate.validate translators , value = .description , update = \newDescription values -> { values | description = newDescription } diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 3c5ed0051..db0a29252 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -7,6 +7,7 @@ import Graphql.Operation exposing (RootMutation) import Graphql.OptionalArgument as OptionalArgument import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) import Json.Encode +import Markdown exposing (Markdown) import Maybe.Extra import Slug exposing (Slug) import Tree @@ -19,9 +20,7 @@ import Tree type alias Model = { id : Id , name : String - - -- TODO - Should this be `Markdown`? - , description : String + , description : Markdown , icon : Maybe String , image : Maybe String , parentId : Maybe Id @@ -30,7 +29,7 @@ type alias Model = create : { name : String - , description : String + , description : Markdown , slug : Slug , parentId : Maybe Id } @@ -53,14 +52,14 @@ create { name, slug, description, parentId } = } ) { name = name - , description = description + , description = Markdown.toRawString description , categories = [] } update : Model - -> { name : String, description : String, slug : Slug } + -> { name : String, description : Markdown, slug : Slug } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation update model { name, description, slug } = @@ -85,7 +84,7 @@ update model { name, description, slug } = } ) { name = name - , description = description + , description = Markdown.toRawString description , categories = [] } @@ -103,7 +102,7 @@ selectionSet = SelectionSet.succeed Model |> SelectionSet.with idSelectionSet |> SelectionSet.with Cambiatus.Object.Category.name - |> SelectionSet.with Cambiatus.Object.Category.description + |> SelectionSet.with (Markdown.selectionSet Cambiatus.Object.Category.description) |> SelectionSet.with Cambiatus.Object.Category.iconUri |> SelectionSet.with Cambiatus.Object.Category.imageUri |> SelectionSet.with (Cambiatus.Object.Category.parentCategory idSelectionSet) @@ -114,7 +113,6 @@ selectionSet = type alias Tree = - -- TODO - Do we want this type alias? Tree.Tree Model From be28303f3c78ccc06f180075d79c87e236283394 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 10 Jun 2022 22:17:21 -0300 Subject: [PATCH 024/104] Change bg, add basic actions panel --- .../Community/Settings/Shop/Categories.elm | 171 ++++++++++++++++-- tailwind.config.js | 5 + 2 files changed, 161 insertions(+), 15 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 7d6cb3eb1..62ff43f1e 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -9,6 +9,7 @@ module Page.Community.Settings.Shop.Categories exposing ) import Api.Graphql.DeleteStatus +import Browser.Events import Community import Dict import EverySet exposing (EverySet) @@ -18,7 +19,7 @@ import Form.Text import Form.Validate import Graphql.Http import Html exposing (Html, button, details, div, li, p, span, summary, text, ul) -import Html.Attributes exposing (class, classList, type_) +import Html.Attributes exposing (class, classList, id, type_) import Html.Events exposing (onClick) import Icons import Json.Decode @@ -29,6 +30,7 @@ import RemoteData exposing (RemoteData) import Session.LoggedIn as LoggedIn import Shop.Category import Slug exposing (Slug) +import Svg exposing (Svg) import Translation import Tree import Tree.Zipper @@ -47,6 +49,7 @@ type alias Model = { expandedCategories : EverySet Shop.Category.Id , newCategoryState : NewCategoryState , categoryModalState : CategoryModalState + , actionsDropdown : Maybe Shop.Category.Id , askingForDeleteConfirmation : Maybe Shop.Category.Id , deleting : EverySet Shop.Category.Id } @@ -58,6 +61,7 @@ init _ = { expandedCategories = EverySet.empty , newCategoryState = NotEditing , categoryModalState = Closed + , actionsDropdown = Nothing , askingForDeleteConfirmation = Nothing , deleting = EverySet.empty } @@ -84,6 +88,7 @@ type CategoryModalState type Msg = NoOp + | PressedEsc | ClickedToggleExpandCategory Shop.Category.Id | ClickedAddCategory (Maybe Shop.Category.Id) | ClickedCancelAddCategory @@ -99,6 +104,8 @@ type Msg | ClosedConfirmDeleteModal | ConfirmedDeleteCategory Shop.Category.Id | CompletedDeletingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) + | ClickedShowActionsDropdown Shop.Category.Id + | ClosedActionsDropdown type alias UpdateResult = @@ -125,6 +132,15 @@ update msg model loggedIn = NoOp -> UR.init model + PressedEsc -> + { model + | newCategoryState = NotEditing + , categoryModalState = Closed + , actionsDropdown = Nothing + , askingForDeleteConfirmation = Nothing + } + |> UR.init + ClickedToggleExpandCategory categoryId -> { model | expandedCategories = @@ -165,11 +181,13 @@ update msg model loggedIn = , description = Form.RichText.initModel "update-category-description" (Just category.description) } ) + , actionsDropdown = Nothing } |> UR.init Nothing -> - UR.init model + { model | actionsDropdown = Nothing } + |> UR.init ClosedCategoryModal -> { model | categoryModalState = Closed } @@ -502,6 +520,21 @@ update msg model loggedIn = model |> UR.init + ClickedShowActionsDropdown categoryId -> + { model + | actionsDropdown = + if model.actionsDropdown == Just categoryId then + Nothing + + else + Just categoryId + } + |> UR.init + + ClosedActionsDropdown -> + { model | actionsDropdown = Nothing } + |> UR.init + -- VIEW @@ -634,6 +667,16 @@ viewCategoryWithChildren translators model zipper children = isAncestorOf (\childId { id } -> childId == id) parentId zipper + + hasActionsMenuOpen = + case model.actionsDropdown of + Nothing -> + False + + Just actionsDropdown -> + isAncestorOf (\childId { id } -> childId == id) + actionsDropdown + zipper in div [ class "transition-colors" @@ -652,7 +695,8 @@ viewCategoryWithChildren translators model zipper children = [ class "marker-hidden flex items-center rounded-sm transition-colors" , classList [ ( "!bg-green/20", isParentOfNewCategoryForm ) - , ( "parent-hover:bg-blue-600/10", not isParentOfNewCategoryForm ) + , ( "parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm ) + , ( "bg-orange-100/20", hasActionsMenuOpen ) ] , Html.Events.preventDefaultOn "click" (Json.Decode.succeed ( NoOp, True )) @@ -664,11 +708,7 @@ viewCategoryWithChildren translators model zipper children = [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) , viewCategory category ] - , button - [ class "h-8 group mr-2" - , onClick (ClickedDeleteCategory category.id) - ] - [ Icons.trash "h-4 text-black group-hover:text-red" ] + , viewActions model category ] , div [ class "ml-4 flex flex-col mb-4 mt-2" ] [ ul @@ -690,7 +730,7 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton customAttrs = button - (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-blue-600/10 rounded-sm" + (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm" :: onClick (ClickedAddCategory parentId) :: customAttrs ) @@ -738,6 +778,82 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs +viewActions : Model -> Shop.Category.Model -> Html Msg +viewActions model category = + let + isDropdownOpen = + case model.actionsDropdown of + Nothing -> + False + + Just actionsDropdown -> + actionsDropdown == category.id + in + div [ class "relative" ] + [ button + [ class "h-8 px-2 rounded-sm transition-colors hover:bg-orange-300/30 active:bg-orange-300/60 action-opener" + , classList [ ( "bg-orange-300/60", isDropdownOpen ) ] + , Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) + ] + -- TODO - Use correct icon + [ Icons.plus "h-4 pointer-events-none" ] + , if not isDropdownOpen then + text "" + + else + ul + [ class "absolute z-10 right-0 bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" + ] + [ li [] + [ viewAction [] + { icon = Icons.edit + + -- TODO - I18N + , label = "Edit main information" + , onClickMsg = ClickedCategory category.id + } + ] + , li [] + [ viewAction [] + { icon = Icons.edit + + -- TODO - I18N + , label = "Edit sharing data" + , onClickMsg = NoOp + } + ] + , li [] + [ viewAction [ class "text-red hover:bg-red/10" ] + { icon = Icons.trash + + -- TODO - I18N + , label = "Delete" + , onClickMsg = ClickedDeleteCategory category.id + } + ] + ] + ] + + +viewAction : + List (Html.Attribute Msg) + -> + { icon : String -> Svg Msg + , label : String + , onClickMsg : Msg + } + -> Html Msg +viewAction containerAttrs { icon, label, onClickMsg } = + button + (class "flex items-center w-full pl-2 pr-8 py-1 rounded-md transition-colors whitespace-nowrap font-bold class hover:bg-gray-200" + :: onClick onClickMsg + :: containerAttrs + ) + [ icon "w-4 mr-2" + , text label + ] + + viewCategoryModal : Translation.Translators -> Shop.Category.Model -> Form.Model UpdateCategoryFormInput -> Html Msg viewCategoryModal translators category formModel = Modal.initWith @@ -971,12 +1087,28 @@ nameAndSlugForm translators { nameFieldId } = subscriptions : Model -> Sub Msg subscriptions model = - case model.newCategoryState of - NotEditing -> - Sub.none - - EditingNewCategory _ -> - Utils.escSubscription ClickedCancelAddCategory + Sub.batch + [ Utils.escSubscription PressedEsc + , case model.actionsDropdown of + Nothing -> + Sub.none + + Just _ -> + Browser.Events.onClick + (Json.Decode.oneOf + [ Json.Decode.at [ "target", "className" ] Json.Decode.string + |> Json.Decode.andThen + (\targetClass -> + if String.contains "action-opener" targetClass then + Json.Decode.fail "" + + else + Json.Decode.succeed ClosedActionsDropdown + ) + , Json.Decode.succeed ClosedActionsDropdown + ] + ) + ] @@ -1029,6 +1161,9 @@ msgToString msg = NoOp -> [ "NoOp" ] + PressedEsc -> + [ "PressedEsc" ] + ClickedToggleExpandCategory _ -> [ "ClickedToggleExpandCategory" ] @@ -1073,3 +1208,9 @@ msgToString msg = CompletedDeletingCategory _ r -> [ "CompletedDeletingCategory", UR.remoteDataToString r ] + + ClickedShowActionsDropdown _ -> + [ "ClickedShowActionsDropdown" ] + + ClosedActionsDropdown -> + [ "ClosedActionsDropdown" ] diff --git a/tailwind.config.js b/tailwind.config.js index 0bad76c6e..5830991c3 100755 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -189,6 +189,10 @@ module.exports = { 'pointer-events': 'auto' } }, + 'appear-from-above-sm': { + '0%': { opacity: 0.25, transform: 'translate(0, -10px)' }, + '100%': { opacity: 1, transform: 'translate(0, 0)' } + }, 'appear-from-above': { '0%': { opacity: 0, transform: 'translate(0, -20px)' }, '100%': { opacity: 1, transform: 'translate(0, 0)' } @@ -218,6 +222,7 @@ module.exports = { }, animation: { 'fade-in': 'appear 50ms linear 400ms both', + 'fade-in-from-above-sm': 'appear-from-above-sm 100ms ease-out both', 'fade-in-from-above': 'appear-from-above 150ms ease-out both', 'fade-in-from-above-lg': 'appear-from-above-lg 600ms ease-in-out both', 'skeleton-loading': 'skeleton-loading-keyframes 1s ease-out infinite alternate', From a8cc2f5e1ac6a696ef0aa6e508c4e85c99f65bf2 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Sat, 11 Jun 2022 01:13:20 -0300 Subject: [PATCH 025/104] Create updateMetadata, include slug in category model --- src/elm/Shop/Category.elm | 49 ++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index db0a29252..50b09e183 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, delete, encodeId, selectionSet, treesSelectionSet, update) +module Shop.Category exposing (Id, Model, Tree, create, delete, encodeId, selectionSet, treesSelectionSet, update, updateMetadata) import Cambiatus.Mutation import Cambiatus.Object @@ -20,6 +20,7 @@ import Tree type alias Model = { id : Id , name : String + , slug : Maybe Slug , description : Markdown , icon : Maybe String , image : Maybe String @@ -63,11 +64,6 @@ update : -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation update model { name, description, slug } = - let - unwrapId : Id -> Int - unwrapId (Id id) = - id - in Cambiatus.Mutation.category (\_ -> { parentCategoryId = @@ -85,6 +81,41 @@ update model { name, description, slug } = ) { name = name , description = Markdown.toRawString description + + -- TODO - Include children + , categories = [] + } + + +updateMetadata : + Model + -> { metaTitle : String, metaDescription : String, metaKeywords : String } + -> SelectionSet decodesTo Cambiatus.Object.Category + -> SelectionSet (Maybe decodesTo) RootMutation +updateMetadata model { metaTitle, metaDescription, metaKeywords } = + Cambiatus.Mutation.category + (\_ -> + -- TODO - Can these arguments be omitted? + { parentCategoryId = + model.parentId + |> Maybe.map unwrapId + |> OptionalArgument.fromMaybe + , iconUri = OptionalArgument.fromMaybe model.icon + , imageUri = OptionalArgument.fromMaybe model.image + , id = OptionalArgument.Present (unwrapId model.id) + , metaDescription = OptionalArgument.Present metaDescription + , metaKeywords = OptionalArgument.Present metaKeywords + , metaTitle = OptionalArgument.Present metaTitle + , slug = + model.slug + |> Maybe.map Slug.toString + |> OptionalArgument.fromMaybe + } + ) + { name = model.name + , description = Markdown.toRawString model.description + + -- TODO - Include children , categories = [] } @@ -102,6 +133,7 @@ selectionSet = SelectionSet.succeed Model |> SelectionSet.with idSelectionSet |> SelectionSet.with Cambiatus.Object.Category.name + |> SelectionSet.with (Cambiatus.Object.Category.slug |> SelectionSet.map (Maybe.andThen Slug.parse)) |> SelectionSet.with (Markdown.selectionSet Cambiatus.Object.Category.description) |> SelectionSet.with Cambiatus.Object.Category.iconUri |> SelectionSet.with Cambiatus.Object.Category.imageUri @@ -170,3 +202,8 @@ idSelectionSet = encodeId : Id -> Json.Encode.Value encodeId (Id id) = Json.Encode.int id + + +unwrapId : Id -> Int +unwrapId (Id id) = + id From d9cba632b0a0a883f29c718dc5c2b053b5d7067d Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Sat, 11 Jun 2022 01:16:17 -0300 Subject: [PATCH 026/104] =?UTF-8?q?Add=20metadata=20form=20with=20a=20nice?= =?UTF-8?q?=20little=20preview=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Community/Settings/Shop/Categories.elm | 287 +++++++++++++++++- 1 file changed, 277 insertions(+), 10 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 62ff43f1e..c40d3375f 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -20,6 +20,7 @@ import Form.Validate import Graphql.Http import Html exposing (Html, button, details, div, li, p, span, summary, text, ul) import Html.Attributes exposing (class, classList, id, type_) +import Html.Attributes.Aria exposing (ariaHidden) import Html.Events exposing (onClick) import Icons import Json.Decode @@ -48,7 +49,8 @@ import View.Modal as Modal type alias Model = { expandedCategories : EverySet Shop.Category.Id , newCategoryState : NewCategoryState - , categoryModalState : CategoryModalState + , categoryModalState : CategoryFormState UpdateCategoryFormInput + , categoryMetadataModalState : CategoryFormState MetadataFormInput , actionsDropdown : Maybe Shop.Category.Id , askingForDeleteConfirmation : Maybe Shop.Category.Id , deleting : EverySet Shop.Category.Id @@ -61,6 +63,7 @@ init _ = { expandedCategories = EverySet.empty , newCategoryState = NotEditing , categoryModalState = Closed + , categoryMetadataModalState = Closed , actionsDropdown = Nothing , askingForDeleteConfirmation = Nothing , deleting = EverySet.empty @@ -81,9 +84,9 @@ type NewCategoryState } -type CategoryModalState +type CategoryFormState formInput = Closed - | Open Shop.Category.Id (Form.Model UpdateCategoryFormInput) + | Open Shop.Category.Id (Form.Model formInput) type Msg @@ -106,6 +109,10 @@ type Msg | CompletedDeletingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) | ClickedShowActionsDropdown Shop.Category.Id | ClosedActionsDropdown + | ClickedOpenMetadataModal Shop.Category.Id + | GotMetadataFormMsg (Form.Msg MetadataFormInput) + | SubmittedMetadataForm MetadataFormOutput + | ClosedMetadataModal type alias UpdateResult = @@ -535,6 +542,65 @@ update msg model loggedIn = { model | actionsDropdown = Nothing } |> UR.init + ClickedOpenMetadataModal categoryId -> + { model + | categoryMetadataModalState = + Open categoryId + (Form.init + -- TODO - Initialize with category's values + { metaTitle = "" + , metaDescription = "" + , metaKeywords = "" + } + ) + } + |> UR.init + + GotMetadataFormMsg subMsg -> + case model.categoryMetadataModalState of + Closed -> + UR.init model + + Open categoryId formModel -> + Form.update loggedIn.shared subMsg formModel + |> UR.fromChild + (\newFormModel -> { model | categoryMetadataModalState = Open categoryId newFormModel }) + GotMetadataFormMsg + LoggedIn.addFeedback + model + + SubmittedMetadataForm formOutput -> + case getCategoryZipper formOutput.id of + Nothing -> + UR.init model + + Just zipper -> + { model + | categoryMetadataModalState = + case model.categoryMetadataModalState of + Closed -> + Closed + + Open categoryId formModel -> + Open categoryId (Form.withDisabled True formModel) + } + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.updateMetadata (Tree.Zipper.label zipper) + { metaTitle = formOutput.metaTitle + , metaDescription = formOutput.metaDescription + , metaKeywords = formOutput.metaKeywords + } + Shop.Category.selectionSet + ) + FinishedUpdatingCategory + ) + + ClosedMetadataModal -> + { model | categoryMetadataModalState = Closed } + |> UR.init + -- VIEW @@ -568,7 +634,7 @@ view loggedIn model = RemoteData.Success ( community, categories ) -> if community.creator == loggedIn.accountName then - view_ loggedIn.shared.translators model categories + view_ loggedIn.shared.translators community model categories else Page.fullPageNotFound (loggedIn.shared.translators.t "community.edit.unauthorized") "" @@ -576,8 +642,8 @@ view loggedIn model = } -view_ : Translation.Translators -> Model -> List Shop.Category.Tree -> Html Msg -view_ translators model categories = +view_ : Translation.Translators -> Community.Model -> Model -> List Shop.Category.Tree -> Html Msg +view_ translators community model categories = div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] [ div [ class "bg-white container mx-auto pt-6 pb-7 px-4 w-full sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] [ ul [ class "mb-2" ] @@ -608,6 +674,17 @@ view_ translators model categories = Just categoryId -> viewConfirmDeleteCategoryModal categoryId + , case model.categoryMetadataModalState of + Closed -> + text "" + + Open categoryId formModel -> + case findInTrees (\category -> category.id == categoryId) categories of + Nothing -> + text "" + + Just openCategory -> + viewCategoryMetadataModal translators community openCategory formModel ] @@ -819,7 +896,7 @@ viewActions model category = -- TODO - I18N , label = "Edit sharing data" - , onClickMsg = NoOp + , onClickMsg = ClickedOpenMetadataModal category.id } ] , li [] @@ -868,8 +945,7 @@ viewCategoryModal translators category formModel = (\_ -> []) (updateCategoryForm translators category.id) formModel - { toMsg = GotUpdateCategoryFormMsg - } + { toMsg = GotUpdateCategoryFormMsg } ] |> Modal.withFooter [ div [ class "flex flex-col w-full sm:flex-row gap-4 items-center justify-center" ] @@ -893,7 +969,51 @@ viewCategoryModal translators category formModel = [ text "Save" ] ] ] - |> Modal.withSize Modal.Large + |> Modal.withSize Modal.FullScreen + |> Modal.toHtml + + +viewCategoryMetadataModal : Translation.Translators -> Community.Model -> Shop.Category.Model -> Form.Model MetadataFormInput -> Html Msg +viewCategoryMetadataModal translators community category formModel = + Modal.initWith + { isVisible = True + , closeMsg = ClosedMetadataModal + } + |> Modal.withHeader "Editing category sharing data" + |> Modal.withBody + [ p [ class "mb-6" ] + -- TODO - I18N + [ text "This information will be used to display rich links when sharing links to this category" ] + , Form.viewWithoutSubmit [ class "mt-2" ] + translators + (\_ -> []) + (metadataForm translators community category.id) + formModel + { toMsg = GotMetadataFormMsg } + ] + |> Modal.withFooter + [ div [ class "flex flex-col w-full sm:flex-row gap-4 items-center justify-center" ] + [ button + [ class "button button-secondary w-full sm:w-40" + , onClick ClosedMetadataModal + ] + -- TODO - I18N + [ text "Cancel" ] + , button + [ class "button button-primary w-full sm:w-40" + , onClick + (Form.parse (metadataForm translators community category.id) + formModel + { onError = GotMetadataFormMsg + , onSuccess = SubmittedMetadataForm + } + ) + ] + -- TODO - I18N + [ text "Save" ] + ] + ] + |> Modal.withSize Modal.FullScreen |> Modal.toHtml @@ -931,6 +1051,58 @@ viewConfirmDeleteCategoryModal categoryId = |> Modal.toHtml +viewShareCategoryPreview : Community.Model -> MetadataFormInput -> Html msg +viewShareCategoryPreview community values = + div [] + [ -- TODO - I18N + p [ class "label" ] [ text "Preview" ] + + -- TODO - I18N + , p [ class "mb-4" ] [ text "This is an aproximation of what the shared content will look like. It will change depending on the platform the link is being shared on." ] + , div [ class "isolate mr-3 z-10 ml-auto w-full sm:w-3/4 md:w-2/3 border border-gray-300 rounded-large relative before:absolute before:bg-white before:border-t before:border-r before:border-gray-300 before:-top-px before:rounded-br-super before:rounded-tr-sm before:-right-2 before:w-8 before:h-4 before:-z-10" ] + [ div [ class "bg-white p-1 rounded-large" ] + [ div [ class "flex w-full bg-gray-100 rounded-large" ] + [ div [ class "bg-gray-200 p-6 rounded-l-large w-1/4 flex-shrink-0 grid place-items-center" ] + [ Icons.image "" ] + , div [ class "py-2 mx-4 w-full" ] + [ if String.isEmpty values.metaTitle then + div [ class "w-3/4 bg-gray-300 rounded font-bold" ] + [ span + [ class "opacity-0 pointer-events-none" + , ariaHidden True + ] + -- This text is only here to define the height + [ text "height" ] + ] + + else + p [ class "font-bold line-clamp-1" ] + [ text values.metaTitle ] + , if String.isEmpty values.metaDescription then + div [ class "w-full bg-gray-200 rounded mt-1 text-sm" ] + [ span + [ class "opacity-0 pointer-events-none" + , ariaHidden True + ] + -- This text is only here to define the height + [ text "height" ] + ] + + else + p [ class "text-sm mt-1 line-clamp-2" ] + [ text values.metaDescription ] + , p [ class "text-sm opacity-70 mt-2 mb-4" ] + [ text community.subdomain ] + ] + ] + , p [ class "mt-2 mb-1 ml-2 text-blue-600" ] + -- TODO - Show correct route in url + [ text ("https://" ++ community.subdomain) ] + ] + ] + ] + + -- FORM @@ -1011,6 +1183,86 @@ updateCategoryForm translators id = ) +type alias MetadataFormInput = + { metaTitle : String + , metaDescription : String + , metaKeywords : String + } + + +type alias MetadataFormOutput = + { id : Shop.Category.Id + , metaTitle : String + , metaDescription : String + + -- TODO - Should this be a List String? + , metaKeywords : String + } + + +metadataForm : Translation.Translators -> Community.Model -> Shop.Category.Id -> Form.Form msg MetadataFormInput MetadataFormOutput +metadataForm translators community categoryId = + Form.succeed + (\metaTitle metaDescription metaKeywords -> + { id = categoryId + , metaTitle = metaTitle + , metaDescription = metaDescription + , metaKeywords = metaKeywords + } + ) + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Title" + , id = "meta-title-input" + } + |> Form.textField + { parser = + Form.Validate.succeed + >> Form.Validate.stringLongerThan 3 + >> Form.Validate.stringShorterThan 40 + >> Form.Validate.validate translators + , value = .metaTitle + , update = \newMetaTitle values -> { values | metaTitle = newMetaTitle } + , externalError = always Nothing + } + ) + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Description" + , id = "meta-description-input" + } + |> Form.textField + { parser = + Form.Validate.succeed + >> Form.Validate.stringLongerThan 3 + >> Form.Validate.stringShorterThan 100 + >> Form.Validate.validate translators + , value = .metaDescription + , update = \newMetaDescription values -> { values | metaDescription = newMetaDescription } + , externalError = always Nothing + } + ) + |> Form.with + (Form.Text.init + -- TODO - I18N + { label = "Keywords" + , id = "meta-keywords-input" + } + |> Form.textField + { parser = + Form.Validate.succeed + -- TODO - Review this validation + >> Form.Validate.validate translators + , value = .metaKeywords + , update = \newMetaKeywords values -> { values | metaKeywords = newMetaKeywords } + , externalError = always Nothing + } + ) + |> Form.withNoOutput ((viewShareCategoryPreview community >> Form.arbitrary) |> Form.introspect) + + nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Slug } nameAndSlugForm translators { nameFieldId } = Form.succeed (\name slug -> { name = name, slug = slug }) @@ -1073,6 +1325,9 @@ nameAndSlugForm translators { nameFieldId } = [ View.Components.label [] -- TODO - I18N { targetId = nameFieldId, labelText = "Slug" } + + -- TODO - We should show a preview of the url, like: + -- TODO - "Your url will look like muda.cambiatus.io/shop/categories/organicos--1234" , text (Slug.toString slug) ] ) @@ -1214,3 +1469,15 @@ msgToString msg = ClosedActionsDropdown -> [ "ClosedActionsDropdown" ] + + ClickedOpenMetadataModal _ -> + [ "ClickedOpenMetadataModal" ] + + GotMetadataFormMsg subMsg -> + "GotMetadataFormMsg" :: Form.msgToString subMsg + + SubmittedMetadataForm _ -> + [ "SubmittedMetadataForm" ] + + ClosedMetadataModal -> + [ "ClosedMetadataModal" ] From a205e9f46950fc1a27cbebd7a9da88497c1f9358 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 13 Jun 2022 10:32:16 -0300 Subject: [PATCH 027/104] Use skeleton loading, adjust spacings --- .../Community/Settings/Shop/Categories.elm | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index c40d3375f..49b6ca13c 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -619,10 +619,10 @@ view loggedIn model = [ Page.viewHeader loggedIn title , case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.NotAsked -> - Page.fullPageLoading loggedIn.shared + viewLoading RemoteData.Loading -> - Page.fullPageLoading loggedIn.shared + viewLoading RemoteData.Failure fieldErr -> case fieldErr of @@ -642,11 +642,43 @@ view loggedIn model = } +viewPageContainer : { children : List (Html Msg), modals : List (Html Msg) } -> Html Msg +viewPageContainer { children, modals } = + div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] + (div [ class "bg-white container mx-auto pt-6 pb-7 px-4 w-full sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] + children + :: modals + ) + + +viewLoading : Html Msg +viewLoading = + let + viewBar : List (Html.Attribute Msg) -> Html Msg + viewBar attributes = + div (class "animate-skeleton-loading max-w-full h-8 rounded-sm mt-2" :: attributes) + [] + in + viewPageContainer + { modals = [] + , children = + [ viewBar [ class "mt-0" ] + , viewBar [ class "ml-4" ] + , viewBar [ class "ml-4" ] + , viewBar [ class "!mt-6" ] + , viewBar [ class "!mt-6" ] + , viewBar [ class "ml-4" ] + , viewBar [ class "ml-8" ] + , viewBar [ class "!mt-6" ] + ] + } + + view_ : Translation.Translators -> Community.Model -> Model -> List Shop.Category.Tree -> Html Msg view_ translators community model categories = - div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] - [ div [ class "bg-white container mx-auto pt-6 pb-7 px-4 w-full sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] - [ ul [ class "mb-2" ] + viewPageContainer + { children = + [ ul [ class "mb-2 grid gap-y-2" ] (List.map (\category -> li [] @@ -657,35 +689,37 @@ view_ translators community model categories = ) , viewAddCategory translators [ class "w-full pl-2" ] model Nothing ] - , case model.categoryModalState of - Closed -> - text "" + , modals = + [ case model.categoryModalState of + Closed -> + text "" - Open categoryId formModel -> - case findInTrees (\category -> category.id == categoryId) categories of - Nothing -> - text "" + Open categoryId formModel -> + case findInTrees (\category -> category.id == categoryId) categories of + Nothing -> + text "" - Just openCategory -> - viewCategoryModal translators openCategory formModel - , case model.askingForDeleteConfirmation of - Nothing -> - text "" + Just openCategory -> + viewCategoryModal translators openCategory formModel + , case model.askingForDeleteConfirmation of + Nothing -> + text "" - Just categoryId -> - viewConfirmDeleteCategoryModal categoryId - , case model.categoryMetadataModalState of - Closed -> - text "" + Just categoryId -> + viewConfirmDeleteCategoryModal categoryId + , case model.categoryMetadataModalState of + Closed -> + text "" - Open categoryId formModel -> - case findInTrees (\category -> category.id == categoryId) categories of - Nothing -> - text "" + Open categoryId formModel -> + case findInTrees (\category -> category.id == categoryId) categories of + Nothing -> + text "" - Just openCategory -> - viewCategoryMetadataModal translators community openCategory formModel - ] + Just openCategory -> + viewCategoryMetadataModal translators community openCategory formModel + ] + } viewCategoryTree : Translation.Translators -> Model -> Shop.Category.Tree -> Html Msg From 8328b60b91bba91fea010eb8c870afc15f4eab59 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 13 Jun 2022 10:58:38 -0300 Subject: [PATCH 028/104] Prevent selection and text wrap --- src/elm/Page/Community/Settings/Shop/Categories.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 49b6ca13c..2670753c1 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -790,7 +790,7 @@ viewCategoryWithChildren translators model zipper children = zipper in div - [ class "transition-colors" + [ class "transition-colors select-none" , classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) ] ] [ details @@ -841,7 +841,7 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton customAttrs = button - (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm" + (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm whitespace-nowrap" :: onClick (ClickedAddCategory parentId) :: customAttrs ) From 82f426e64b767a0cc55af7352af25e16602c259f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 10:43:57 -0300 Subject: [PATCH 029/104] Add drag-drop-touch polyfill --- config/polyfills.js | 4 ++++ package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/config/polyfills.js b/config/polyfills.js index ad0a665d9..d3b56ce8b 100755 --- a/config/polyfills.js +++ b/config/polyfills.js @@ -14,6 +14,10 @@ require('whatwg-fetch') // Some browsers still don't support the focus-visible css class require('focus-visible') +// Mobile browsers don't support the drag and drop API. This polyfill listens to +// touch events in order to simulate that on mobile devices +require('drag-drop-touch') + // Object.assign() is commonly used with React. // It will use the native implementation if it's present and isn't buggy. Object.assign = require('object-assign') diff --git a/package.json b/package.json index edffce58c..2583796c5 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "copy-webpack-plugin": "^5.0.4", "create-elm-app": "^4.0.0", "css-loader": "^0.28.9", + "drag-drop-touch": "^1.3.1", "elm": "^0.19.1-5", "elm-asset-webpack-loader": "^1.1.1", "elm-format": "^0.8.5", diff --git a/yarn.lock b/yarn.lock index 46696d88a..379e63813 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4356,6 +4356,11 @@ dotenv@^5.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== +drag-drop-touch@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/drag-drop-touch/-/drag-drop-touch-1.3.1.tgz#bf1a6a89c0e793a04a524ca2317301d684375c7e" + integrity sha512-Q0/ZgsnW7VUjn+YqSnp1rvxjjPnZX5YLyVaw28einood+eTMcLzgOgHk8nyqIF9O18J68l+2htlEnbw5GsyTvQ== + duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" From f5e6effc3768e27989adb26ad25c2941d605205d Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 10:44:51 -0300 Subject: [PATCH 030/104] Update graphql types --- src/elm/Cambiatus/InputObject.elm | 48 +++++++++++++++++++++++-------- src/elm/Cambiatus/Mutation.elm | 26 +++++++---------- src/elm/Shop.elm | 12 +++++--- src/elm/Shop/Category.elm | 31 ++++++++------------ 4 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/elm/Cambiatus/InputObject.elm b/src/elm/Cambiatus/InputObject.elm index 3708c0f28..1b29f5d78 100644 --- a/src/elm/Cambiatus/InputObject.elm +++ b/src/elm/Cambiatus/InputObject.elm @@ -329,30 +329,29 @@ encodeNewCommunityInput input = buildProductsFilterInput : - ProductsFilterInputRequiredFields - -> (ProductsFilterInputOptionalFields -> ProductsFilterInputOptionalFields) + (ProductsFilterInputOptionalFields -> ProductsFilterInputOptionalFields) -> ProductsFilterInput -buildProductsFilterInput required fillOptionals = +buildProductsFilterInput fillOptionals = let optionals = fillOptionals - { inStock = Absent } + { account = Absent, categoriesIds = Absent, inStock = Absent } in - { account = required.account, inStock = optionals.inStock } - - -type alias ProductsFilterInputRequiredFields = - { account : String } + { account = optionals.account, categoriesIds = optionals.categoriesIds, inStock = optionals.inStock } type alias ProductsFilterInputOptionalFields = - { inStock : OptionalArgument Bool } + { account : OptionalArgument String + , categoriesIds : OptionalArgument (List (Maybe Int)) + , inStock : OptionalArgument Bool + } {-| Type for the ProductsFilterInput input object. -} type alias ProductsFilterInput = - { account : String + { account : OptionalArgument String + , categoriesIds : OptionalArgument (List (Maybe Int)) , inStock : OptionalArgument Bool } @@ -362,7 +361,7 @@ type alias ProductsFilterInput = encodeProductsFilterInput : ProductsFilterInput -> Value encodeProductsFilterInput input = Encode.maybeObject - [ ( "account", Encode.string input.account |> Just ), ( "inStock", Encode.bool |> Encode.optional input.inStock ) ] + [ ( "account", Encode.string |> Encode.optional input.account ), ( "categoriesIds", (Encode.int |> Encode.maybe |> Encode.list) |> Encode.optional input.categoriesIds ), ( "inStock", Encode.bool |> Encode.optional input.inStock ) ] buildPushSubscriptionInput : @@ -421,6 +420,31 @@ encodeReadNotificationInput input = [ ( "id", Encode.int input.id |> Just ) ] +buildSubcategoryInput : + SubcategoryInputRequiredFields + -> SubcategoryInput +buildSubcategoryInput required = + { id = required.id } + + +type alias SubcategoryInputRequiredFields = + { id : Int } + + +{-| Type for the SubcategoryInput input object. +-} +type alias SubcategoryInput = + { id : Int } + + +{-| Encode a SubcategoryInput into a value that can be used as an argument. +-} +encodeSubcategoryInput : SubcategoryInput -> Value +encodeSubcategoryInput input = + Encode.maybeObject + [ ( "id", Encode.int input.id |> Just ) ] + + buildTransferDirection : (TransferDirectionOptionalFields -> TransferDirectionOptionalFields) -> TransferDirection diff --git a/src/elm/Cambiatus/Mutation.elm b/src/elm/Cambiatus/Mutation.elm index 83973b345..aee2d0744 100644 --- a/src/elm/Cambiatus/Mutation.elm +++ b/src/elm/Cambiatus/Mutation.elm @@ -48,44 +48,40 @@ addCommunityPhotos requiredArgs object_ = type alias CategoryOptionalArguments = - { iconUri : OptionalArgument String + { categories : OptionalArgument (List Cambiatus.InputObject.SubcategoryInput) + , description : OptionalArgument String + , iconUri : OptionalArgument String , id : OptionalArgument Int , imageUri : OptionalArgument String , metaDescription : OptionalArgument String , metaKeywords : OptionalArgument String , metaTitle : OptionalArgument String - , parentCategoryId : OptionalArgument Int + , name : OptionalArgument String + , parentId : OptionalArgument Int , slug : OptionalArgument String } -type alias CategoryRequiredArguments = - { categories : List Int - , description : String - , name : String - } - - {-| [Auth required - Admin only] Upserts a category - - parentCategoryId - Parent category ID + - categories - List of subcategories; Associates given IDs as subcategories to this category + - parentId - Parent category ID -} category : (CategoryOptionalArguments -> CategoryOptionalArguments) - -> CategoryRequiredArguments -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -category fillInOptionals requiredArgs object_ = +category fillInOptionals object_ = let filledInOptionals = - fillInOptionals { iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, parentCategoryId = Absent, slug = Absent } + fillInOptionals { categories = Absent, description = Absent, iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, name = Absent, parentId = Absent, slug = Absent } optionalArgs = - [ Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "parentCategoryId" filledInOptionals.parentCategoryId Encode.int, Argument.optional "slug" filledInOptionals.slug Encode.string ] + [ Argument.optional "categories" filledInOptionals.categories (Cambiatus.InputObject.encodeSubcategoryInput |> Encode.list), Argument.optional "description" filledInOptionals.description Encode.string, Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "name" filledInOptionals.name Encode.string, Argument.optional "parentId" filledInOptionals.parentId Encode.int, Argument.optional "slug" filledInOptionals.slug Encode.string ] |> List.filterMap identity in - Object.selectionForCompositeField "category" (optionalArgs ++ [ Argument.required "categories" requiredArgs.categories (Encode.int |> Encode.list), Argument.required "description" requiredArgs.description Encode.string, Argument.required "name" requiredArgs.name Encode.string ]) object_ (identity >> Decode.nullable) + Object.selectionForCompositeField "category" optionalArgs object_ (identity >> Decode.nullable) type alias CommunityRequiredArguments = diff --git a/src/elm/Shop.elm b/src/elm/Shop.elm index dc6b33bda..1eafa709f 100755 --- a/src/elm/Shop.elm +++ b/src/elm/Shop.elm @@ -235,10 +235,14 @@ productsQuery filter accName communityId = case filter of UserSales -> let - args = - \_ -> - { filters = Present { account = Eos.nameToString accName, inStock = Absent } - } + args _ = + { filters = + Present + { account = Present (Eos.nameToString accName) + , inStock = Absent + , categoriesIds = Absent + } + } in Query.products args { communityId = Eos.symbolToString communityId } productSelectionSet diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 50b09e183..af2db8bf5 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -39,7 +39,7 @@ create : create { name, slug, description, parentId } = Cambiatus.Mutation.category (\_ -> - { parentCategoryId = + { parentId = parentId |> Maybe.map (\(Id id) -> id) |> OptionalArgument.fromMaybe @@ -50,12 +50,11 @@ create { name, slug, description, parentId } = , metaKeywords = OptionalArgument.Absent , metaTitle = OptionalArgument.Absent , slug = OptionalArgument.Present (Slug.toString slug) + , categories = OptionalArgument.Absent + , name = OptionalArgument.Present name + , description = OptionalArgument.Present (Markdown.toRawString description) } ) - { name = name - , description = Markdown.toRawString description - , categories = [] - } update : @@ -66,7 +65,7 @@ update : update model { name, description, slug } = Cambiatus.Mutation.category (\_ -> - { parentCategoryId = + { parentId = model.parentId |> Maybe.map unwrapId |> OptionalArgument.fromMaybe @@ -77,14 +76,11 @@ update model { name, description, slug } = , metaKeywords = OptionalArgument.Absent , metaTitle = OptionalArgument.Absent , slug = OptionalArgument.Present (Slug.toString slug) + , name = OptionalArgument.Present name + , description = OptionalArgument.Present (Markdown.toRawString description) + , categories = OptionalArgument.Absent } ) - { name = name - , description = Markdown.toRawString description - - -- TODO - Include children - , categories = [] - } updateMetadata : @@ -96,7 +92,7 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = Cambiatus.Mutation.category (\_ -> -- TODO - Can these arguments be omitted? - { parentCategoryId = + { parentId = model.parentId |> Maybe.map unwrapId |> OptionalArgument.fromMaybe @@ -110,14 +106,11 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = model.slug |> Maybe.map Slug.toString |> OptionalArgument.fromMaybe + , name = OptionalArgument.Absent + , description = OptionalArgument.Absent + , categories = OptionalArgument.Absent } ) - { name = model.name - , description = Markdown.toRawString model.description - - -- TODO - Include children - , categories = [] - } delete : From 622c42c14e257a9d817b51689213420a755cba32 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 16:15:01 -0300 Subject: [PATCH 031/104] Update parentId -> parent --- src/elm/Cambiatus/Object/Category.elm | 6 +++--- src/elm/Shop/Category.elm | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/elm/Cambiatus/Object/Category.elm b/src/elm/Cambiatus/Object/Category.elm index d1495a551..16439394f 100644 --- a/src/elm/Cambiatus/Object/Category.elm +++ b/src/elm/Cambiatus/Object/Category.elm @@ -71,11 +71,11 @@ name = Object.selectionForField "String" "name" [] Decode.string -parentCategory : +parent : SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) Cambiatus.Object.Category -parentCategory object_ = - Object.selectionForCompositeField "parentCategory" [] object_ (identity >> Decode.nullable) +parent object_ = + Object.selectionForCompositeField "parent" [] object_ (identity >> Decode.nullable) products : diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index af2db8bf5..636aa1986 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -130,7 +130,7 @@ selectionSet = |> SelectionSet.with (Markdown.selectionSet Cambiatus.Object.Category.description) |> SelectionSet.with Cambiatus.Object.Category.iconUri |> SelectionSet.with Cambiatus.Object.Category.imageUri - |> SelectionSet.with (Cambiatus.Object.Category.parentCategory idSelectionSet) + |> SelectionSet.with (Cambiatus.Object.Category.parent idSelectionSet) From e6ee1690f9cc3c2eaeb00d1d27f19946f6a9f352 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 17:46:45 -0300 Subject: [PATCH 032/104] Implement basic drag and drop functionality --- src/elm/Dnd.elm | 153 ++++++++++++++++++ .../Community/Settings/Shop/Categories.elm | 121 ++++++++++++-- 2 files changed, 258 insertions(+), 16 deletions(-) create mode 100644 src/elm/Dnd.elm diff --git a/src/elm/Dnd.elm b/src/elm/Dnd.elm new file mode 100644 index 000000000..4fa7fdb77 --- /dev/null +++ b/src/elm/Dnd.elm @@ -0,0 +1,153 @@ +module Dnd exposing (ExtMsg(..), Model, Msg, draggable, dropZone, getDraggingElement, getDraggingOverElement, init, msgToString, update) + +import Html +import Html.Attributes +import Html.Events +import Json.Decode +import UpdateResult as UR + + +type Model draggable dropZone + = Model + { draggingElement : Maybe draggable + , draggingOverElement : Maybe dropZone + } + + +init : Model draggable dropZone +init = + Model + { draggingElement = Nothing + , draggingOverElement = Nothing + } + + +type Msg draggable dropZone + = StartedDragging draggable + | StoppedDragging + | DroppedOn dropZone + | DraggedOver dropZone + + +type alias UpdateResult draggable dropZone = + UR.UpdateResult (Model draggable dropZone) (Msg draggable dropZone) (ExtMsg draggable dropZone) + + +type ExtMsg draggable dropZone + = Dropped { draggedElement : draggable, dropZone : dropZone } + + +update : Msg draggable dropZone -> Model draggable dropZone -> UpdateResult draggable dropZone +update msg (Model model) = + case msg of + StartedDragging elementId -> + { model | draggingElement = Just elementId } + |> Model + |> UR.init + + StoppedDragging -> + { model | draggingElement = Nothing } + |> Model + |> UR.init + + DroppedOn elementId -> + case model.draggingElement of + Nothing -> + model + |> Model + |> UR.init + + Just draggingElement -> + model + |> Model + |> UR.init + |> UR.addExt (Dropped { draggedElement = draggingElement, dropZone = elementId }) + + DraggedOver elementId -> + { model | draggingOverElement = Just elementId } + |> Model + |> UR.init + + + +-- DRAGGABLE ATTRIBUTES + + +onDragStart : draggable -> Html.Attribute (Msg draggable dropZone) +onDragStart draggableId = + Html.Events.on "dragstart" (Json.Decode.succeed (StartedDragging draggableId)) + + +onDragEnd : Html.Attribute (Msg draggable dropZone) +onDragEnd = + Html.Events.on "dragend" (Json.Decode.succeed StoppedDragging) + + +draggableAttr : Html.Attribute (Msg draggable dropZone) +draggableAttr = + Html.Attributes.draggable "true" + + +draggable : draggable -> (Msg draggable dropZone -> msg) -> List (Html.Attribute msg) +draggable draggableId toMsg = + [ onDragStart draggableId + , onDragEnd + , draggableAttr + ] + |> List.map (Html.Attributes.map toMsg) + + + +-- DROP ZONE ATTRIBUTES + + +onDrop : dropZone -> Html.Attribute (Msg draggable dropZone) +onDrop dropZoneId = + Html.Events.preventDefaultOn "drop" (Json.Decode.succeed ( DroppedOn dropZoneId, True )) + + +onDragOver : dropZone -> Html.Attribute (Msg draggable dropZone) +onDragOver dropZoneId = + Html.Events.preventDefaultOn "dragover" (Json.Decode.succeed ( DraggedOver dropZoneId, True )) + + +dropZone : dropZone -> (Msg draggable dropZone -> msg) -> List (Html.Attribute msg) +dropZone dropZoneId toMsg = + [ onDrop dropZoneId + , onDragOver dropZoneId + ] + |> List.map (Html.Attributes.map toMsg) + + + +-- GETTERS + + +getDraggingElement : Model draggable dropZone -> Maybe draggable +getDraggingElement (Model model) = + model.draggingElement + + +getDraggingOverElement : Model draggable dropZone -> Maybe dropZone +getDraggingOverElement (Model model) = + model.draggingOverElement + + + +-- UTILS + + +msgToString : Msg draggable dropZone -> List String +msgToString msg = + case msg of + StartedDragging _ -> + [ "StartedDragging" ] + + StoppedDragging -> + [ "StoppedDragging" ] + + DroppedOn _ -> + [ "DroppedOn" ] + + DraggedOver _ -> + [ "DraggedOver" ] diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 2670753c1..1a3cf7ac7 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -12,6 +12,7 @@ import Api.Graphql.DeleteStatus import Browser.Events import Community import Dict +import Dnd import EverySet exposing (EverySet) import Form import Form.RichText @@ -54,9 +55,14 @@ type alias Model = , actionsDropdown : Maybe Shop.Category.Id , askingForDeleteConfirmation : Maybe Shop.Category.Id , deleting : EverySet Shop.Category.Id + , dnd : Dnd.Model Shop.Category.Id DropZone } +type DropZone + = OnTopOf Shop.Category.Id + + init : LoggedIn.Model -> UpdateResult init _ = -- TODO - Should we start them all expanded? @@ -67,6 +73,7 @@ init _ = , actionsDropdown = Nothing , askingForDeleteConfirmation = Nothing , deleting = EverySet.empty + , dnd = Dnd.init } |> UR.init |> UR.addExt (LoggedIn.RequestedReloadCommunityField Community.ShopCategoriesField) @@ -113,6 +120,7 @@ type Msg | GotMetadataFormMsg (Form.Msg MetadataFormInput) | SubmittedMetadataForm MetadataFormOutput | ClosedMetadataModal + | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) type alias UpdateResult = @@ -601,6 +609,53 @@ update msg model loggedIn = { model | categoryMetadataModalState = Closed } |> UR.init + GotDndMsg subMsg -> + Dnd.update subMsg model.dnd + |> UR.fromChild + (\newDnd -> { model | dnd = newDnd }) + GotDndMsg + (updateDnd loggedIn) + model + + +updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult +updateDnd loggedIn ext ur = + case ext of + Dnd.Dropped { draggedElement, dropZone } -> + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + case findInForest (\{ id } -> id == draggedElement) categories of + Nothing -> + ur + + Just draggedTree -> + let + insertInForest : Tree.Zipper.Zipper Shop.Category.Model -> List Shop.Category.Tree + insertInForest forest = + case dropZone of + OnTopOf parentId -> + forest + |> Tree.Zipper.findFromRoot (\{ id } -> id == parentId) + |> Maybe.map + (Tree.Zipper.mapTree (Tree.prependChild (Tree.Zipper.tree draggedTree)) + >> Tree.Zipper.toForest + >> (\( first, others ) -> first :: others) + ) + |> Maybe.withDefault categories + in + ur + |> UR.addExt + (draggedTree + |> Tree.Zipper.removeTree + |> Maybe.map insertInForest + |> Maybe.withDefault [] + |> Community.ShopCategories + |> LoggedIn.SetCommunityField + ) + + _ -> + ur + -- VIEW @@ -687,7 +742,7 @@ view_ translators community model categories = ) categories ) - , viewAddCategory translators [ class "w-full pl-2" ] model Nothing + , viewAddCategory translators [ class "w-full" ] model Nothing ] , modals = [ case model.categoryModalState of @@ -788,6 +843,22 @@ viewCategoryWithChildren translators model zipper children = isAncestorOf (\childId { id } -> childId == id) actionsDropdown zipper + + isDescendantOfDraggingCategory = + case + Dnd.getDraggingElement model.dnd + |> Maybe.andThen + (\draggingId -> + Tree.Zipper.findFromRoot (\{ id } -> draggingId == id) + zipper + ) + of + Nothing -> + False + + Just draggingZipper -> + isAncestorOf (\child { id } -> child == id) category.id draggingZipper + && ((Tree.Zipper.label draggingZipper).id /= category.id) in div [ class "transition-colors select-none" @@ -803,19 +874,23 @@ viewCategoryWithChildren translators model zipper children = , classList [ ( "pointer-events-none", EverySet.member category.id model.deleting ) ] ] [ summary - [ class "marker-hidden flex items-center rounded-sm transition-colors" - , classList - [ ( "!bg-green/20", isParentOfNewCategoryForm ) - , ( "parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm ) - , ( "bg-orange-100/20", hasActionsMenuOpen ) - ] - , Html.Events.preventDefaultOn "click" - (Json.Decode.succeed ( NoOp, True )) - ] - [ button - [ onClick (ClickedToggleExpandCategory category.id) - , class "flex items-center w-full" - ] + (class "marker-hidden flex items-center rounded-sm transition-colors cursor-pointer" + :: classList + [ ( "!bg-green/20", isParentOfNewCategoryForm ) + , ( "parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm ) + , ( "bg-orange-100/20", hasActionsMenuOpen ) + , ( "!bg-gray-200", isDescendantOfDraggingCategory ) + ] + :: onClick (ClickedToggleExpandCategory category.id) + :: Dnd.draggable category.id GotDndMsg + ++ (if isDescendantOfDraggingCategory then + [] + + else + Dnd.dropZone (OnTopOf category.id) GotDndMsg + ) + ) + [ div [ class "flex items-center w-full" ] [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) , viewCategory category ] @@ -865,7 +940,7 @@ viewAddCategory translators attrs model maybeParentCategory = Form.view (class "bg-white border border-gray-300 rounded-md p-4" :: attrs) translators (\submitButton -> - [ div [ class "flex flex-col sm:flex-row justify-end gap-4" ] + [ div [ class "flex flex-col sm:flex-row justify-end gap-4 mt-10" ] [ button [ class "button button-secondary w-full sm:w-40" , type_ "button" @@ -957,7 +1032,7 @@ viewAction : viewAction containerAttrs { icon, label, onClickMsg } = button (class "flex items-center w-full pl-2 pr-8 py-1 rounded-md transition-colors whitespace-nowrap font-bold class hover:bg-gray-200" - :: onClick onClickMsg + :: Utils.onClickNoBubble onClickMsg :: containerAttrs ) [ icon "w-4 mr-2" @@ -1404,6 +1479,17 @@ subscriptions model = -- UTILS +getDraggingOverCategoryId : Model -> Maybe Shop.Category.Id +getDraggingOverCategoryId model = + Dnd.getDraggingOverElement model.dnd + |> Maybe.map + (\dropZone -> + case dropZone of + OnTopOf id -> + id + ) + + findInTrees : (a -> Bool) -> List (Tree.Tree a) -> Maybe a findInTrees fn trees = trees @@ -1515,3 +1601,6 @@ msgToString msg = ClosedMetadataModal -> [ "ClosedMetadataModal" ] + + GotDndMsg subMsg -> + "GotDndMsg" :: Dnd.msgToString subMsg From 9ee06c70f7ac89f67118eff05f63791a1906fa6f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 20:05:26 -0300 Subject: [PATCH 033/104] Show valid drop zones, perform validations --- src/elm/Dnd.elm | 16 ++- .../Community/Settings/Shop/Categories.elm | 98 ++++++++++++++----- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/elm/Dnd.elm b/src/elm/Dnd.elm index 4fa7fdb77..375a4bad9 100644 --- a/src/elm/Dnd.elm +++ b/src/elm/Dnd.elm @@ -103,12 +103,24 @@ draggable draggableId toMsg = onDrop : dropZone -> Html.Attribute (Msg draggable dropZone) onDrop dropZoneId = - Html.Events.preventDefaultOn "drop" (Json.Decode.succeed ( DroppedOn dropZoneId, True )) + Html.Events.custom "drop" + (Json.Decode.succeed + { message = DroppedOn dropZoneId + , stopPropagation = True + , preventDefault = True + } + ) onDragOver : dropZone -> Html.Attribute (Msg draggable dropZone) onDragOver dropZoneId = - Html.Events.preventDefaultOn "dragover" (Json.Decode.succeed ( DraggedOver dropZoneId, True )) + Html.Events.custom "dragover" + (Json.Decode.succeed + { message = DraggedOver dropZoneId + , stopPropagation = True + , preventDefault = True + } + ) dropZone : dropZone -> (Msg draggable dropZone -> msg) -> List (Html.Attribute msg) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 1a3cf7ac7..678e07560 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -27,6 +27,7 @@ import Icons import Json.Decode import List.Extra import Markdown exposing (Markdown) +import Maybe.Extra import Page import RemoteData exposing (RemoteData) import Session.LoggedIn as LoggedIn @@ -844,26 +845,61 @@ viewCategoryWithChildren translators model zipper children = actionsDropdown zipper - isDescendantOfDraggingCategory = - case - Dnd.getDraggingElement model.dnd - |> Maybe.andThen - (\draggingId -> - Tree.Zipper.findFromRoot (\{ id } -> draggingId == id) - zipper - ) - of + isValidDropzone = + case Dnd.getDraggingElement model.dnd of + Nothing -> + True + + Just draggingId -> + let + isDraggingChild = + Tree.Zipper.children zipper + |> List.any + (\child -> + Tree.label child + |> .id + |> (==) draggingId + ) + + isDraggingItself = + draggingId == category.id + + isDraggingAncestor = + Tree.Zipper.findFromRoot (\{ id } -> id == draggingId) zipper + |> Maybe.map + (\draggingZipper -> + isAncestorOf2 category.id (Tree.Zipper.tree draggingZipper) + ) + |> Maybe.withDefault False + in + not isDraggingItself && not isDraggingChild && not isDraggingAncestor + + isDraggingSomething = + Dnd.getDraggingElement model.dnd + |> Maybe.Extra.isJust + + isDraggingOver = + case Dnd.getDraggingOverElement model.dnd of Nothing -> False - Just draggingZipper -> - isAncestorOf (\child { id } -> child == id) category.id draggingZipper - && ((Tree.Zipper.label draggingZipper).id /= category.id) + Just (OnTopOf draggingOverId) -> + draggingOverId == category.id in div - [ class "transition-colors select-none" - , classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) ] - ] + (class "transition-colors select-none rounded-sm border border-dashed border-transparent" + :: classList + [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) + , ( "!bg-green/30", isValidDropzone && isDraggingSomething ) + , ( "border-black", isValidDropzone && isDraggingSomething && isDraggingOver ) + ] + :: (if isValidDropzone then + Dnd.dropZone (OnTopOf category.id) GotDndMsg + + else + [] + ) + ) [ details [ if isOpen then Html.Attributes.attribute "open" "true" @@ -877,18 +913,11 @@ viewCategoryWithChildren translators model zipper children = (class "marker-hidden flex items-center rounded-sm transition-colors cursor-pointer" :: classList [ ( "!bg-green/20", isParentOfNewCategoryForm ) - , ( "parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm ) + , ( "parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) , ( "bg-orange-100/20", hasActionsMenuOpen ) - , ( "!bg-gray-200", isDescendantOfDraggingCategory ) ] :: onClick (ClickedToggleExpandCategory category.id) :: Dnd.draggable category.id GotDndMsg - ++ (if isDescendantOfDraggingCategory then - [] - - else - Dnd.dropZone (OnTopOf category.id) GotDndMsg - ) ) [ div [ class "flex items-center w-full" ] [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) @@ -1508,6 +1537,29 @@ findInForest fn trees = |> Tree.Zipper.findFromRoot fn +isAncestorOf2 : Shop.Category.Id -> Shop.Category.Tree -> Bool +isAncestorOf2 childId parentTree = + let + parentId = + Tree.label parentTree |> .id + + isItself = + childId == parentId + + isIndirectAncestor = + Tree.children parentTree + |> List.any + (\childTree -> + if childId == (Tree.label childTree |> .id) then + True + + else + isAncestorOf2 childId childTree + ) + in + isItself || isIndirectAncestor + + isAncestorOf : (child -> a -> Bool) -> child -> Tree.Zipper.Zipper a -> Bool isAncestorOf equals child parentZipper = let From 3324e83927e9bc76d9fa2dad879fb260f1767f5c Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 20:06:31 -0300 Subject: [PATCH 034/104] Refactor isAncestorOf --- .../Community/Settings/Shop/Categories.elm | 40 +++++-------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 678e07560..a994c3f28 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -831,9 +831,9 @@ viewCategoryWithChildren translators model zipper children = False Just parentId -> - isAncestorOf (\childId { id } -> childId == id) + isAncestorOf parentId - zipper + (Tree.Zipper.tree zipper) hasActionsMenuOpen = case model.actionsDropdown of @@ -841,9 +841,9 @@ viewCategoryWithChildren translators model zipper children = False Just actionsDropdown -> - isAncestorOf (\childId { id } -> childId == id) + isAncestorOf actionsDropdown - zipper + (Tree.Zipper.tree zipper) isValidDropzone = case Dnd.getDraggingElement model.dnd of @@ -867,8 +867,8 @@ viewCategoryWithChildren translators model zipper children = isDraggingAncestor = Tree.Zipper.findFromRoot (\{ id } -> id == draggingId) zipper |> Maybe.map - (\draggingZipper -> - isAncestorOf2 category.id (Tree.Zipper.tree draggingZipper) + (Tree.Zipper.tree + >> isAncestorOf category.id ) |> Maybe.withDefault False in @@ -1537,8 +1537,8 @@ findInForest fn trees = |> Tree.Zipper.findFromRoot fn -isAncestorOf2 : Shop.Category.Id -> Shop.Category.Tree -> Bool -isAncestorOf2 childId parentTree = +isAncestorOf : Shop.Category.Id -> Shop.Category.Tree -> Bool +isAncestorOf childId parentTree = let parentId = Tree.label parentTree |> .id @@ -1554,34 +1554,12 @@ isAncestorOf2 childId parentTree = True else - isAncestorOf2 childId childTree + isAncestorOf childId childTree ) in isItself || isIndirectAncestor -isAncestorOf : (child -> a -> Bool) -> child -> Tree.Zipper.Zipper a -> Bool -isAncestorOf equals child parentZipper = - let - isDirectParent = - equals child (Tree.Zipper.label parentZipper) - - isIndirectAncestor = - Tree.Zipper.children parentZipper - |> List.any - (\childTree -> - if equals child (Tree.label childTree) then - True - - else - isAncestorOf equals - child - (Tree.Zipper.fromTree childTree) - ) - in - isDirectParent || isIndirectAncestor - - msgToString : Msg -> List String msgToString msg = case msg of From b0017975edfc00d2cad03b3c0e218d27c0854702 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 20:30:52 -0300 Subject: [PATCH 035/104] Expand categories when dragging over them --- src/elm/Dnd.elm | 13 ++++---- .../Community/Settings/Shop/Categories.elm | 30 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/elm/Dnd.elm b/src/elm/Dnd.elm index 375a4bad9..91d238bc7 100644 --- a/src/elm/Dnd.elm +++ b/src/elm/Dnd.elm @@ -26,7 +26,7 @@ type Msg draggable dropZone = StartedDragging draggable | StoppedDragging | DroppedOn dropZone - | DraggedOver dropZone + | DraggedOverInternal dropZone type alias UpdateResult draggable dropZone = @@ -35,6 +35,7 @@ type alias UpdateResult draggable dropZone = type ExtMsg draggable dropZone = Dropped { draggedElement : draggable, dropZone : dropZone } + | DraggedOver dropZone update : Msg draggable dropZone -> Model draggable dropZone -> UpdateResult draggable dropZone @@ -63,10 +64,12 @@ update msg (Model model) = |> UR.init |> UR.addExt (Dropped { draggedElement = draggingElement, dropZone = elementId }) - DraggedOver elementId -> - { model | draggingOverElement = Just elementId } + DraggedOverInternal dropZone_ -> + -- TODO - Only fire DraggedOver if changed + { model | draggingOverElement = Just dropZone_ } |> Model |> UR.init + |> UR.addExt (DraggedOver dropZone_) @@ -116,7 +119,7 @@ onDragOver : dropZone -> Html.Attribute (Msg draggable dropZone) onDragOver dropZoneId = Html.Events.custom "dragover" (Json.Decode.succeed - { message = DraggedOver dropZoneId + { message = DraggedOverInternal dropZoneId , stopPropagation = True , preventDefault = True } @@ -161,5 +164,5 @@ msgToString msg = DroppedOn _ -> [ "DroppedOn" ] - DraggedOver _ -> + DraggedOverInternal _ -> [ "DraggedOver" ] diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index a994c3f28..933e9f12f 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -29,11 +29,13 @@ import List.Extra import Markdown exposing (Markdown) import Maybe.Extra import Page +import Process import RemoteData exposing (RemoteData) import Session.LoggedIn as LoggedIn import Shop.Category import Slug exposing (Slug) import Svg exposing (Svg) +import Task import Translation import Tree import Tree.Zipper @@ -122,6 +124,7 @@ type Msg | SubmittedMetadataForm MetadataFormOutput | ClosedMetadataModal | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) + | DraggedOverCategoryForAWhile Shop.Category.Id type alias UpdateResult = @@ -618,6 +621,19 @@ update msg model loggedIn = (updateDnd loggedIn) model + DraggedOverCategoryForAWhile categoryId -> + case Dnd.getDraggingOverElement model.dnd of + Nothing -> + UR.init model + + Just (OnTopOf currentCategoryId) -> + if currentCategoryId == categoryId then + { model | expandedCategories = EverySet.insert categoryId model.expandedCategories } + |> UR.init + + else + UR.init model + updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult updateDnd loggedIn ext ur = @@ -657,6 +673,17 @@ updateDnd loggedIn ext ur = _ -> ur + Dnd.DraggedOver (OnTopOf categoryId) -> + let + millisToWait = + 250 + in + ur + |> UR.addCmd + (Process.sleep millisToWait + |> Task.perform (\_ -> DraggedOverCategoryForAWhile categoryId) + ) + -- VIEW @@ -1634,3 +1661,6 @@ msgToString msg = GotDndMsg subMsg -> "GotDndMsg" :: Dnd.msgToString subMsg + + DraggedOverCategoryForAWhile _ -> + [ "DraggedOverCategoryForAWhile" ] From 59057c63b56d9ab98c8bbe3054e4779d3b574c26 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 20:38:15 -0300 Subject: [PATCH 036/104] Listen to dragLeave event --- src/elm/Dnd.elm | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/elm/Dnd.elm b/src/elm/Dnd.elm index 91d238bc7..766429266 100644 --- a/src/elm/Dnd.elm +++ b/src/elm/Dnd.elm @@ -26,6 +26,7 @@ type Msg draggable dropZone = StartedDragging draggable | StoppedDragging | DroppedOn dropZone + | DragLeft dropZone | DraggedOverInternal dropZone @@ -64,12 +65,31 @@ update msg (Model model) = |> UR.init |> UR.addExt (Dropped { draggedElement = draggingElement, dropZone = elementId }) + DragLeft dropZone_ -> + { model + | draggingOverElement = + if model.draggingOverElement == Just dropZone_ then + Nothing + + else + model.draggingOverElement + } + |> Model + |> UR.init + DraggedOverInternal dropZone_ -> - -- TODO - Only fire DraggedOver if changed + let + fireDraggedOver = + if model.draggingOverElement == Just dropZone_ then + identity + + else + UR.addExt (DraggedOver dropZone_) + in { model | draggingOverElement = Just dropZone_ } |> Model |> UR.init - |> UR.addExt (DraggedOver dropZone_) + |> fireDraggedOver @@ -126,10 +146,16 @@ onDragOver dropZoneId = ) +onDragLeave : dropZone -> Html.Attribute (Msg draggable dropZone) +onDragLeave dropZoneId = + Html.Events.preventDefaultOn "dragleave" (Json.Decode.succeed ( DragLeft dropZoneId, True )) + + dropZone : dropZone -> (Msg draggable dropZone -> msg) -> List (Html.Attribute msg) dropZone dropZoneId toMsg = [ onDrop dropZoneId , onDragOver dropZoneId + , onDragLeave dropZoneId ] |> List.map (Html.Attributes.map toMsg) @@ -164,5 +190,8 @@ msgToString msg = DroppedOn _ -> [ "DroppedOn" ] + DragLeft _ -> + [ "DragLeft" ] + DraggedOverInternal _ -> [ "DraggedOver" ] From b8219cc51583186d3d7ed074e61c037a8b4ca839 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 21:22:21 -0300 Subject: [PATCH 037/104] Perform mutation when dropping elements --- .../Community/Settings/Shop/Categories.elm | 110 ++++++++++++++---- src/elm/Shop/Category.elm | 26 ++++- 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 933e9f12f..6f4eaf3c0 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -68,7 +68,6 @@ type DropZone init : LoggedIn.Model -> UpdateResult init _ = - -- TODO - Should we start them all expanded? { expandedCategories = EverySet.empty , newCategoryState = NotEditing , categoryModalState = Closed @@ -125,6 +124,7 @@ type Msg | ClosedMetadataModal | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) | DraggedOverCategoryForAWhile Shop.Category.Id + | CompletedMovingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) type alias UpdateResult = @@ -634,42 +634,99 @@ update msg model loggedIn = else UR.init model - -updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult -updateDnd loggedIn ext ur = - case ext of - Dnd.Dropped { draggedElement, dropZone } -> + CompletedMovingCategory categoryId (RemoteData.Success (Just parentId)) -> case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.Success ( _, categories ) -> - case findInForest (\{ id } -> id == draggedElement) categories of + case findInForest (\{ id } -> id == categoryId) categories of Nothing -> - ur + UR.init model - Just draggedTree -> + Just childZipper -> let - insertInForest : Tree.Zipper.Zipper Shop.Category.Model -> List Shop.Category.Tree - insertInForest forest = - case dropZone of - OnTopOf parentId -> - forest - |> Tree.Zipper.findFromRoot (\{ id } -> id == parentId) - |> Maybe.map - (Tree.Zipper.mapTree (Tree.prependChild (Tree.Zipper.tree draggedTree)) - >> Tree.Zipper.toForest - >> (\( first, others ) -> first :: others) + zipperWithMovedChild = + childZipper + |> Tree.Zipper.removeTree + |> Maybe.andThen + (Tree.Zipper.findFromRoot (\{ id } -> id == parentId) + >> Maybe.map + (Tree.Zipper.mapTree + (Tree.appendChild (Tree.Zipper.tree childZipper) + >> Tree.mapChildren (List.sortBy (Tree.label >> .name)) + ) ) - |> Maybe.withDefault categories + ) + |> Maybe.withDefault childZipper in - ur + model + |> UR.init |> UR.addExt - (draggedTree - |> Tree.Zipper.removeTree - |> Maybe.map insertInForest - |> Maybe.withDefault [] + (zipperWithMovedChild + |> Tree.Zipper.toForest + |> (\( first, others ) -> first :: others) |> Community.ShopCategories |> LoggedIn.SetCommunityField ) + _ -> + model + |> UR.init + + CompletedMovingCategory categoryId (RemoteData.Success Nothing) -> + UR.init model + |> UR.logImpossible msg + "Got Nothing when trying to move a child category" + (Just loggedIn.accountName) + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [ { name = "Category" + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + } + ] + + CompletedMovingCategory categoryId (RemoteData.Failure err) -> + UR.init model + |> UR.logGraphqlError msg + (Just loggedIn.accountName) + "Got an error when trying to move a child category" + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [ { name = "Category" + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + } + ] + err + -- TODO - I18N + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Something went wrong :(") + + CompletedMovingCategory _ _ -> + UR.init model + + +updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult +updateDnd loggedIn ext ur = + case ext of + Dnd.Dropped { draggedElement, dropZone } -> + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + case dropZone of + OnTopOf parentId -> + case findInForest (\{ id } -> id == parentId) categories of + Nothing -> + ur + + Just parentZipper -> + UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.addChild (Tree.Zipper.tree parentZipper) + draggedElement + Shop.Category.idSelectionSet + ) + (CompletedMovingCategory draggedElement) + ) + ur + _ -> ur @@ -1664,3 +1721,6 @@ msgToString msg = DraggedOverCategoryForAWhile _ -> [ "DraggedOverCategoryForAWhile" ] + + CompletedMovingCategory _ r -> + [ "CompletedMovingCategory", UR.remoteDataToString r ] diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 636aa1986..ea923dc62 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, create, delete, encodeId, selectionSet, treesSelectionSet, update, updateMetadata) +module Shop.Category exposing (Id, Model, Tree, addChild, create, delete, encodeId, idSelectionSet, selectionSet, treesSelectionSet, update, updateMetadata) import Cambiatus.Mutation import Cambiatus.Object @@ -113,6 +113,30 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = ) +addChild : Tree -> Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation +addChild tree (Id newChildId) = + Cambiatus.Mutation.category + (\optionals -> + { optionals + | categories = + Tree.children tree + |> List.map + (Tree.label + >> .id + >> unwrapId + >> (\id -> { id = id }) + ) + |> (\existingChildren -> existingChildren ++ [ { id = newChildId } ]) + |> OptionalArgument.Present + , id = + Tree.label tree + |> .id + |> unwrapId + |> OptionalArgument.Present + } + ) + + delete : Id -> SelectionSet decodesTo Cambiatus.Object.DeleteStatus From 14cdea0512a7ef1e3cf698d34342873ce3d3ed5b Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 21:44:30 -0300 Subject: [PATCH 038/104] Allow moving categories to the root --- .../Community/Settings/Shop/Categories.elm | 142 +++++++++++++++--- src/elm/Shop/Category.elm | 13 +- 2 files changed, 135 insertions(+), 20 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 6f4eaf3c0..41caad757 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -19,6 +19,7 @@ import Form.RichText import Form.Text import Form.Validate import Graphql.Http +import Graphql.SelectionSet import Html exposing (Html, button, details, div, li, p, span, summary, text, ul) import Html.Attributes exposing (class, classList, id, type_) import Html.Attributes.Aria exposing (ariaHidden) @@ -64,6 +65,7 @@ type alias Model = type DropZone = OnTopOf Shop.Category.Id + | OnRoot init : LoggedIn.Model -> UpdateResult @@ -125,6 +127,7 @@ type Msg | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) | DraggedOverCategoryForAWhile Shop.Category.Id | CompletedMovingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) + | CompletedMovingCategoryToRoot Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) type alias UpdateResult = @@ -281,8 +284,7 @@ update msg model loggedIn = Just zipper -> zipper |> Tree.Zipper.mapTree (Tree.appendChild (Tree.singleton category)) - |> Tree.Zipper.toForest - |> (\( first, others ) -> first :: others) + |> toFlatForest insertInCommunity : UpdateResult -> UpdateResult insertInCommunity = @@ -397,8 +399,7 @@ update msg model loggedIn = Just zipper -> zipper |> Tree.Zipper.replaceLabel category - |> Tree.Zipper.toForest - |> (\( first, others ) -> first :: others) + |> toFlatForest |> Community.ShopCategories |> LoggedIn.SetCommunityField |> UR.addExt @@ -483,7 +484,7 @@ update msg model loggedIn = removeFromForest zipper = zipper |> Tree.Zipper.removeTree - |> Maybe.map (Tree.Zipper.toForest >> (\( first, others ) -> first :: others)) + |> Maybe.map toFlatForest |> Maybe.withDefault [] removeFromCommunity : UpdateResult -> UpdateResult @@ -634,6 +635,9 @@ update msg model loggedIn = else UR.init model + Just OnRoot -> + UR.init model + CompletedMovingCategory categoryId (RemoteData.Success (Just parentId)) -> case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.Success ( _, categories ) -> @@ -661,8 +665,7 @@ update msg model loggedIn = |> UR.init |> UR.addExt (zipperWithMovedChild - |> Tree.Zipper.toForest - |> (\( first, others ) -> first :: others) + |> toFlatForest |> Community.ShopCategories |> LoggedIn.SetCommunityField ) @@ -703,6 +706,68 @@ update msg model loggedIn = CompletedMovingCategory _ _ -> UR.init model + CompletedMovingCategoryToRoot categoryId (RemoteData.Success (Just ())) -> + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + case findInForest (\{ id } -> id == categoryId) categories of + Nothing -> + UR.init model + + Just childZipper -> + let + zipperWithChildOnRoot = + childZipper + |> Tree.Zipper.removeTree + |> Maybe.map + (toFlatForest + >> (\forest -> Tree.Zipper.tree childZipper :: forest) + >> List.sortBy (Tree.label >> .name) + ) + |> Maybe.withDefault (toFlatForest childZipper) + in + model + |> UR.init + |> UR.addExt + (zipperWithChildOnRoot + |> Community.ShopCategories + |> LoggedIn.SetCommunityField + ) + + _ -> + UR.init model + + CompletedMovingCategoryToRoot categoryId (RemoteData.Success Nothing) -> + UR.init model + |> UR.logImpossible msg + "Got Nothing when trying to move a child category to root" + (Just loggedIn.accountName) + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [ { name = "Category" + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + } + ] + + CompletedMovingCategoryToRoot categoryId (RemoteData.Failure err) -> + UR.init model + |> UR.logGraphqlError msg + (Just loggedIn.accountName) + "Got an error when trying to move a child category to root" + { moduleName = "Page.Community.Settings.Shop.Categories" + , function = "update" + } + [ { name = "Category" + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + } + ] + err + -- TODO - I18N + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Something went wrong :(") + + CompletedMovingCategoryToRoot _ _ -> + UR.init model + updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult updateDnd loggedIn ext ur = @@ -727,6 +792,16 @@ updateDnd loggedIn ext ur = ) ur + OnRoot -> + UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.moveToRoot draggedElement + (Graphql.SelectionSet.succeed ()) + ) + (CompletedMovingCategoryToRoot draggedElement) + ) + ur + _ -> ur @@ -741,6 +816,9 @@ updateDnd loggedIn ext ur = |> Task.perform (\_ -> DraggedOverCategoryForAWhile categoryId) ) + Dnd.DraggedOver OnRoot -> + ur + -- VIEW @@ -816,6 +894,21 @@ viewLoading = view_ : Translation.Translators -> Community.Model -> Model -> List Shop.Category.Tree -> Html Msg view_ translators community model categories = + let + isDraggingSomething = + Maybe.Extra.isJust (Dnd.getDraggingElement model.dnd) + + isDraggingOverAddCategory = + case Dnd.getDraggingOverElement model.dnd of + Nothing -> + False + + Just (OnTopOf _) -> + False + + Just OnRoot -> + True + in viewPageContainer { children = [ ul [ class "mb-2 grid gap-y-2" ] @@ -827,7 +920,16 @@ view_ translators community model categories = ) categories ) - , viewAddCategory translators [ class "w-full" ] model Nothing + , viewAddCategory translators + (class "w-full border border-dashed border-transparent" + :: classList + [ ( "bg-green/30", isDraggingSomething ) + , ( "border-black", isDraggingSomething && isDraggingOverAddCategory ) + ] + :: Dnd.dropZone OnRoot GotDndMsg + ) + model + Nothing ] , modals = [ case model.categoryModalState of @@ -969,6 +1071,9 @@ viewCategoryWithChildren translators model zipper children = Just (OnTopOf draggingOverId) -> draggingOverId == category.id + + Just OnRoot -> + False in div (class "transition-colors select-none rounded-sm border border-dashed border-transparent" @@ -1592,17 +1697,6 @@ subscriptions model = -- UTILS -getDraggingOverCategoryId : Model -> Maybe Shop.Category.Id -getDraggingOverCategoryId model = - Dnd.getDraggingOverElement model.dnd - |> Maybe.map - (\dropZone -> - case dropZone of - OnTopOf id -> - id - ) - - findInTrees : (a -> Bool) -> List (Tree.Tree a) -> Maybe a findInTrees fn trees = trees @@ -1621,6 +1715,13 @@ findInForest fn trees = |> Tree.Zipper.findFromRoot fn +toFlatForest : Tree.Zipper.Zipper a -> List (Tree.Tree a) +toFlatForest zipper = + zipper + |> Tree.Zipper.toForest + |> (\( first, others ) -> first :: others) + + isAncestorOf : Shop.Category.Id -> Shop.Category.Tree -> Bool isAncestorOf childId parentTree = let @@ -1724,3 +1825,6 @@ msgToString msg = CompletedMovingCategory _ r -> [ "CompletedMovingCategory", UR.remoteDataToString r ] + + CompletedMovingCategoryToRoot _ r -> + [ "CompletedMovingCategoryToRoot", UR.remoteDataToString r ] diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index ea923dc62..c33f8d3bb 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,4 @@ -module Shop.Category exposing (Id, Model, Tree, addChild, create, delete, encodeId, idSelectionSet, selectionSet, treesSelectionSet, update, updateMetadata) +module Shop.Category exposing (Id, Model, Tree, addChild, create, delete, encodeId, idSelectionSet, moveToRoot, selectionSet, treesSelectionSet, update, updateMetadata) import Cambiatus.Mutation import Cambiatus.Object @@ -137,6 +137,17 @@ addChild tree (Id newChildId) = ) +moveToRoot : Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation +moveToRoot (Id id) = + Cambiatus.Mutation.category + (\optionals -> + { optionals + | id = OptionalArgument.Present id + , parentId = OptionalArgument.Null + } + ) + + delete : Id -> SelectionSet decodesTo Cambiatus.Object.DeleteStatus From 91677b0391023c191dc11f059931fae223145c2a Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 21:46:48 -0300 Subject: [PATCH 039/104] Refactor mutations to use the optionals object --- src/elm/Shop/Category.elm | 70 ++++++++++++--------------------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index c33f8d3bb..a7697c8db 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -38,21 +38,15 @@ create : -> SelectionSet (Maybe decodesTo) RootMutation create { name, slug, description, parentId } = Cambiatus.Mutation.category - (\_ -> - { parentId = - parentId - |> Maybe.map (\(Id id) -> id) - |> OptionalArgument.fromMaybe - , iconUri = OptionalArgument.Absent - , imageUri = OptionalArgument.Absent - , id = OptionalArgument.Absent - , metaDescription = OptionalArgument.Absent - , metaKeywords = OptionalArgument.Absent - , metaTitle = OptionalArgument.Absent - , slug = OptionalArgument.Present (Slug.toString slug) - , categories = OptionalArgument.Absent - , name = OptionalArgument.Present name - , description = OptionalArgument.Present (Markdown.toRawString description) + (\optionals -> + { optionals + | parentId = + parentId + |> Maybe.map (\(Id id) -> id) + |> OptionalArgument.fromMaybe + , slug = OptionalArgument.Present (Slug.toString slug) + , name = OptionalArgument.Present name + , description = OptionalArgument.Present (Markdown.toRawString description) } ) @@ -64,21 +58,12 @@ update : -> SelectionSet (Maybe decodesTo) RootMutation update model { name, description, slug } = Cambiatus.Mutation.category - (\_ -> - { parentId = - model.parentId - |> Maybe.map unwrapId - |> OptionalArgument.fromMaybe - , iconUri = OptionalArgument.fromMaybe model.icon - , imageUri = OptionalArgument.fromMaybe model.image - , id = OptionalArgument.Present (unwrapId model.id) - , metaDescription = OptionalArgument.Absent - , metaKeywords = OptionalArgument.Absent - , metaTitle = OptionalArgument.Absent - , slug = OptionalArgument.Present (Slug.toString slug) - , name = OptionalArgument.Present name - , description = OptionalArgument.Present (Markdown.toRawString description) - , categories = OptionalArgument.Absent + (\optionals -> + { optionals + | id = OptionalArgument.Present (unwrapId model.id) + , slug = OptionalArgument.Present (Slug.toString slug) + , name = OptionalArgument.Present name + , description = OptionalArgument.Present (Markdown.toRawString description) } ) @@ -90,25 +75,12 @@ updateMetadata : -> SelectionSet (Maybe decodesTo) RootMutation updateMetadata model { metaTitle, metaDescription, metaKeywords } = Cambiatus.Mutation.category - (\_ -> - -- TODO - Can these arguments be omitted? - { parentId = - model.parentId - |> Maybe.map unwrapId - |> OptionalArgument.fromMaybe - , iconUri = OptionalArgument.fromMaybe model.icon - , imageUri = OptionalArgument.fromMaybe model.image - , id = OptionalArgument.Present (unwrapId model.id) - , metaDescription = OptionalArgument.Present metaDescription - , metaKeywords = OptionalArgument.Present metaKeywords - , metaTitle = OptionalArgument.Present metaTitle - , slug = - model.slug - |> Maybe.map Slug.toString - |> OptionalArgument.fromMaybe - , name = OptionalArgument.Absent - , description = OptionalArgument.Absent - , categories = OptionalArgument.Absent + (\optionals -> + { optionals + | id = OptionalArgument.Present (unwrapId model.id) + , metaDescription = OptionalArgument.Present metaDescription + , metaKeywords = OptionalArgument.Present metaKeywords + , metaTitle = OptionalArgument.Present metaTitle } ) From 8b0bafebca83dbf4b39eb13bdc8e813120eb019e Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 21:49:21 -0300 Subject: [PATCH 040/104] Fix according to elm-review --- src/elm/Dnd.elm | 2 +- src/elm/Page/Community/Settings/Shop/Categories.elm | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/elm/Dnd.elm b/src/elm/Dnd.elm index 766429266..c5f989add 100644 --- a/src/elm/Dnd.elm +++ b/src/elm/Dnd.elm @@ -194,4 +194,4 @@ msgToString msg = [ "DragLeft" ] DraggedOverInternal _ -> - [ "DraggedOver" ] + [ "DraggedOverInternal" ] diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 41caad757..119d0c0ea 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -101,8 +101,7 @@ type CategoryFormState formInput type Msg - = NoOp - | PressedEsc + = PressedEsc | ClickedToggleExpandCategory Shop.Category.Id | ClickedAddCategory (Maybe Shop.Category.Id) | ClickedCancelAddCategory @@ -151,9 +150,6 @@ update msg model loggedIn = Nothing in case msg of - NoOp -> - UR.init model - PressedEsc -> { model | newCategoryState = NotEditing @@ -1748,9 +1744,6 @@ isAncestorOf childId parentTree = msgToString : Msg -> List String msgToString msg = case msg of - NoOp -> - [ "NoOp" ] - PressedEsc -> [ "PressedEsc" ] From eb02b0d624ef1b472ef9aa2e8e4445faca088c2f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 22:12:16 -0300 Subject: [PATCH 041/104] Add move up and down buttons, implement move up --- .../Community/Settings/Shop/Categories.elm | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 119d0c0ea..8673f4731 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -127,6 +127,8 @@ type Msg | DraggedOverCategoryForAWhile Shop.Category.Id | CompletedMovingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) | CompletedMovingCategoryToRoot Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) + | ClickedMoveUp Shop.Category.Id + | ClickedMoveDown Shop.Category.Id type alias UpdateResult = @@ -764,6 +766,47 @@ update msg model loggedIn = CompletedMovingCategoryToRoot _ _ -> UR.init model + ClickedMoveUp categoryId -> + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + case findInForest (\{ id } -> id == categoryId) categories of + Nothing -> + UR.init model + + Just zipper -> + case + Tree.Zipper.parent zipper + |> Maybe.andThen Tree.Zipper.parent + of + Nothing -> + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.moveToRoot categoryId + (Graphql.SelectionSet.succeed ()) + ) + (CompletedMovingCategoryToRoot categoryId) + ) + + Just grandParentZipper -> + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.addChild (Tree.Zipper.tree grandParentZipper) + categoryId + Shop.Category.idSelectionSet + ) + (CompletedMovingCategory categoryId) + ) + + _ -> + UR.init model + + ClickedMoveDown categoryId -> + UR.init model + updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult updateDnd loggedIn ext ur = @@ -1222,6 +1265,24 @@ viewActions model category = , onClickMsg = ClickedOpenMetadataModal category.id } ] + , li [] + [ viewAction [] + -- TODO - Hide when parent is root + -- TODO - Use correct icon + { icon = \classes -> Icons.arrowDown ("rotate-180 " ++ classes) + , label = "Move up" + , onClickMsg = ClickedMoveUp category.id + } + ] + , li [] + [ viewAction [] + -- TODO - Hide when impossible + -- TODO - Use correct icon + { icon = Icons.arrowDown + , label = "Move down" + , onClickMsg = ClickedMoveDown category.id + } + ] , li [] [ viewAction [ class "text-red hover:bg-red/10" ] { icon = Icons.trash @@ -1244,6 +1305,7 @@ viewAction : } -> Html Msg viewAction containerAttrs { icon, label, onClickMsg } = + -- TODO - Focus classes button (class "flex items-center w-full pl-2 pr-8 py-1 rounded-md transition-colors whitespace-nowrap font-bold class hover:bg-gray-200" :: Utils.onClickNoBubble onClickMsg @@ -1821,3 +1883,9 @@ msgToString msg = CompletedMovingCategoryToRoot _ r -> [ "CompletedMovingCategoryToRoot", UR.remoteDataToString r ] + + ClickedMoveUp _ -> + [ "ClickedMoveUp" ] + + ClickedMoveDown _ -> + [ "ClickedMoveDown" ] From 67718c443ff1c1623c5a01cf229b3f99b0de6df0 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 14 Jun 2022 22:31:32 -0300 Subject: [PATCH 042/104] Implement move down --- .../Community/Settings/Shop/Categories.elm | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 8673f4731..4c4720f3e 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -774,10 +774,7 @@ update msg model loggedIn = UR.init model Just zipper -> - case - Tree.Zipper.parent zipper - |> Maybe.andThen Tree.Zipper.parent - of + case goUpWithoutChildren zipper of Nothing -> model |> UR.init @@ -790,6 +787,7 @@ update msg model loggedIn = ) Just grandParentZipper -> + -- TODO - Expand categories so the moved category is still visible model |> UR.init |> UR.addExt @@ -805,7 +803,40 @@ update msg model loggedIn = UR.init model ClickedMoveDown categoryId -> - UR.init model + case Community.getField loggedIn.selectedCommunity .shopCategories of + RemoteData.Success ( _, categories ) -> + case findInForest (\{ id } -> id == categoryId) categories of + Nothing -> + UR.init model + + Just zipper -> + case goDownWithoutChildren zipper of + Nothing -> + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.moveToRoot categoryId + (Graphql.SelectionSet.succeed ()) + ) + (CompletedMovingCategoryToRoot categoryId) + ) + + Just newParentZipper -> + -- TODO - Expand categories so the moved category is still visible + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.addChild (Tree.Zipper.tree newParentZipper) + categoryId + Shop.Category.idSelectionSet + ) + (CompletedMovingCategory categoryId) + ) + + _ -> + UR.init model updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult @@ -1780,6 +1811,30 @@ toFlatForest zipper = |> (\( first, others ) -> first :: others) +goUpWithoutChildren : Tree.Zipper.Zipper a -> Maybe (Tree.Zipper.Zipper a) +goUpWithoutChildren zipper = + Tree.Zipper.backward zipper + |> Maybe.andThen + (\backwardZipper -> + if Tree.Zipper.parent zipper == Just backwardZipper then + Tree.Zipper.parent backwardZipper + + else + Just backwardZipper + ) + + +goDownWithoutChildren : Tree.Zipper.Zipper a -> Maybe (Tree.Zipper.Zipper a) +goDownWithoutChildren zipper = + case Tree.Zipper.nextSibling zipper of + Nothing -> + Tree.Zipper.parent zipper + |> Maybe.andThen Tree.Zipper.parent + + Just firstSibling -> + Just firstSibling + + isAncestorOf : Shop.Category.Id -> Shop.Category.Tree -> Bool isAncestorOf childId parentTree = let From 387d3bd5ab1dd6bc22258eeb4bf775e0100172b1 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 15 Jun 2022 10:06:49 -0300 Subject: [PATCH 043/104] Expand categories when moving child --- .../Community/Settings/Shop/Categories.elm | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 4c4720f3e..26173bc75 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -652,14 +652,28 @@ update msg model loggedIn = (Tree.Zipper.findFromRoot (\{ id } -> id == parentId) >> Maybe.map (Tree.Zipper.mapTree - (Tree.appendChild (Tree.Zipper.tree childZipper) + (Tree.prependChild (Tree.Zipper.tree childZipper) >> Tree.mapChildren (List.sortBy (Tree.label >> .name)) ) ) ) |> Maybe.withDefault childZipper in - model + { model + | expandedCategories = + zipperWithMovedChild + |> Tree.Zipper.findFromRoot (\{ id } -> id == categoryId) + |> Maybe.map + (getAllAncestors + >> List.foldl + (Tree.Zipper.label + >> .id + >> EverySet.insert + ) + model.expandedCategories + ) + |> Maybe.withDefault model.expandedCategories + } |> UR.init |> UR.addExt (zipperWithMovedChild @@ -787,7 +801,6 @@ update msg model loggedIn = ) Just grandParentZipper -> - -- TODO - Expand categories so the moved category is still visible model |> UR.init |> UR.addExt @@ -823,7 +836,6 @@ update msg model loggedIn = ) Just newParentZipper -> - -- TODO - Expand categories so the moved category is still visible model |> UR.init |> UR.addExt @@ -1835,6 +1847,21 @@ goDownWithoutChildren zipper = Just firstSibling +getAllAncestors : Tree.Zipper.Zipper a -> List (Tree.Zipper.Zipper a) +getAllAncestors zipper = + getAllAncestorsHelper zipper [] + + +getAllAncestorsHelper : Tree.Zipper.Zipper a -> List (Tree.Zipper.Zipper a) -> List (Tree.Zipper.Zipper a) +getAllAncestorsHelper zipper ancestors = + case Tree.Zipper.parent zipper of + Nothing -> + ancestors + + Just parent -> + getAllAncestorsHelper parent (parent :: ancestors) + + isAncestorOf : Shop.Category.Id -> Shop.Category.Tree -> Bool isAncestorOf childId parentTree = let From a75f4a96826ba928711442d11c2ec93c8c72c602 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 15 Jun 2022 11:19:26 -0300 Subject: [PATCH 044/104] Hide move up and down buttons when it's not possible to go up or down --- .../Community/Settings/Shop/Categories.elm | 108 ++++++++++++------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 26173bc75..dc0ff7960 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -993,15 +993,25 @@ view_ translators community model categories = in viewPageContainer { children = - [ ul [ class "mb-2 grid gap-y-2" ] - (List.map - (\category -> - li [] - [ viewCategoryTree translators model category - ] - ) - categories - ) + [ case categories of + [] -> + -- TODO - Show something when there are no categories yet + text "" + + first :: others -> + let + rootZipper = + Tree.Zipper.fromForest first others + in + ul [ class "mb-2 grid gap-y-2" ] + (List.map + (\category -> + li [] + [ viewCategoryTree translators model rootZipper category + ] + ) + categories + ) , viewAddCategory translators (class "w-full border border-dashed border-transparent" :: classList @@ -1046,12 +1056,13 @@ view_ translators community model categories = } -viewCategoryTree : Translation.Translators -> Model -> Shop.Category.Tree -> Html Msg -viewCategoryTree translators model rootTree = - let - rootZipper = - Tree.Zipper.fromTree rootTree - in +viewCategoryTree : + Translation.Translators + -> Model + -> Tree.Zipper.Zipper Shop.Category.Model + -> Shop.Category.Tree + -> Html Msg +viewCategoryTree translators model rootZipper currentTree = Tree.restructure (\category -> rootZipper @@ -1059,7 +1070,7 @@ viewCategoryTree translators model rootTree = |> Maybe.withDefault (Tree.singleton category |> Tree.Zipper.fromTree) ) (viewCategoryWithChildren translators model) - rootTree + currentTree viewCategory : Shop.Category.Model -> Html Msg @@ -1194,7 +1205,7 @@ viewCategoryWithChildren translators model zipper children = [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) , viewCategory category ] - , viewActions model category + , viewActions model zipper ] , div [ class "ml-4 flex flex-col mb-4 mt-2" ] [ ul @@ -1264,9 +1275,12 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs -viewActions : Model -> Shop.Category.Model -> Html Msg -viewActions model category = +viewActions : Model -> Tree.Zipper.Zipper Shop.Category.Model -> Html Msg +viewActions model zipper = let + category = + Tree.Zipper.label zipper + isDropdownOpen = case model.actionsDropdown of Nothing -> @@ -1274,6 +1288,18 @@ viewActions model category = Just actionsDropdown -> actionsDropdown == category.id + + canGoDown = + not + (Maybe.Extra.isNothing (goDownWithoutChildren zipper) + && Maybe.Extra.isNothing (Tree.Zipper.parent zipper) + ) + + canGoUp = + not + (Maybe.Extra.isNothing (goUpWithoutChildren zipper) + && Maybe.Extra.isNothing (Tree.Zipper.parent zipper) + ) in div [ class "relative" ] [ button @@ -1308,24 +1334,30 @@ viewActions model category = , onClickMsg = ClickedOpenMetadataModal category.id } ] - , li [] - [ viewAction [] - -- TODO - Hide when parent is root - -- TODO - Use correct icon - { icon = \classes -> Icons.arrowDown ("rotate-180 " ++ classes) - , label = "Move up" - , onClickMsg = ClickedMoveUp category.id - } - ] - , li [] - [ viewAction [] - -- TODO - Hide when impossible - -- TODO - Use correct icon - { icon = Icons.arrowDown - , label = "Move down" - , onClickMsg = ClickedMoveDown category.id - } - ] + , if canGoUp then + li [] + [ viewAction [] + -- TODO - Use correct icon + { icon = \classes -> Icons.arrowDown ("rotate-180 " ++ classes) + , label = "Move up" + , onClickMsg = ClickedMoveUp category.id + } + ] + + else + text "" + , if canGoDown then + li [] + [ viewAction [] + -- TODO - Use correct icon + { icon = Icons.arrowDown + , label = "Move down" + , onClickMsg = ClickedMoveDown category.id + } + ] + + else + text "" , li [] [ viewAction [ class "text-red hover:bg-red/10" ] { icon = Icons.trash @@ -1836,7 +1868,7 @@ goUpWithoutChildren zipper = ) -goDownWithoutChildren : Tree.Zipper.Zipper a -> Maybe (Tree.Zipper.Zipper a) +goDownWithoutChildren : Tree.Zipper.Zipper Shop.Category.Model -> Maybe (Tree.Zipper.Zipper Shop.Category.Model) goDownWithoutChildren zipper = case Tree.Zipper.nextSibling zipper of Nothing -> From d9ae9876ad85718090b317866dd41c6cd175baef Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 15 Jun 2022 11:25:34 -0300 Subject: [PATCH 045/104] Initialize metadata form with category data, close modal when done saving --- .../Community/Settings/Shop/Categories.elm | 38 ++++++++++++------- src/elm/Shop/Category.elm | 6 +++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index dc0ff7960..8921c2db3 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -376,7 +376,6 @@ update msg model loggedIn = |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.update (Tree.Zipper.label zipper) - -- TODO - Include children { name = formOutput.name , slug = formOutput.slug , description = formOutput.description @@ -402,7 +401,10 @@ update msg model loggedIn = |> LoggedIn.SetCommunityField |> UR.addExt in - { model | categoryModalState = Closed } + { model + | categoryModalState = Closed + , categoryMetadataModalState = Closed + } |> UR.init |> updateInCommunity @@ -554,18 +556,26 @@ update msg model loggedIn = |> UR.init ClickedOpenMetadataModal categoryId -> - { model - | categoryMetadataModalState = - Open categoryId - (Form.init - -- TODO - Initialize with category's values - { metaTitle = "" - , metaDescription = "" - , metaKeywords = "" - } - ) - } - |> UR.init + case getCategoryZipper categoryId of + Nothing -> + UR.init model + + Just zipper -> + let + category = + Tree.Zipper.label zipper + in + { model + | categoryMetadataModalState = + Open categoryId + (Form.init + { metaTitle = Maybe.withDefault "" category.metaTitle + , metaDescription = Maybe.withDefault "" category.metaDescription + , metaKeywords = Maybe.withDefault "" category.metaKeywords + } + ) + } + |> UR.init GotMetadataFormMsg subMsg -> case model.categoryMetadataModalState of diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index a7697c8db..906408bc2 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -25,6 +25,9 @@ type alias Model = , icon : Maybe String , image : Maybe String , parentId : Maybe Id + , metaTitle : Maybe String + , metaDescription : Maybe String + , metaKeywords : Maybe String } @@ -138,6 +141,9 @@ selectionSet = |> SelectionSet.with Cambiatus.Object.Category.iconUri |> SelectionSet.with Cambiatus.Object.Category.imageUri |> SelectionSet.with (Cambiatus.Object.Category.parent idSelectionSet) + |> SelectionSet.with Cambiatus.Object.Category.metaTitle + |> SelectionSet.with Cambiatus.Object.Category.metaDescription + |> SelectionSet.with Cambiatus.Object.Category.metaKeywords From 0478221cbf1a56f2bdca4b99c6b3f1d60a544c1b Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 17 Jun 2022 14:26:42 -0300 Subject: [PATCH 046/104] Add settings card --- src/elm/Page/Community/Settings/Settings.elm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/elm/Page/Community/Settings/Settings.elm b/src/elm/Page/Community/Settings/Settings.elm index 9aee16e50..d08c0fbf8 100644 --- a/src/elm/Page/Community/Settings/Settings.elm +++ b/src/elm/Page/Community/Settings/Settings.elm @@ -103,6 +103,9 @@ viewSettingsList shared community = else text "" + + -- TODO - I18N + , settingCard "Shop categories" (t "menu.edit") "What kinds of items people can put for sale" Route.CommunitySettingsShopCategories , settingCard (t "settings.contacts.title") (t "menu.edit") (t "settings.contacts.description") Route.CommunitySettingsContacts , settingCard (t "settings.features.title") (t "menu.edit") (t "settings.features.description") Route.CommunitySettingsFeatures , if Maybe.Extra.isJust community.contributionConfiguration then From caa47b5ae99d2b307c87c6f45ea2c344de66c5f2 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 20 Jun 2022 22:23:49 -0300 Subject: [PATCH 047/104] Add ids to description input id --- .../Community/Settings/Shop/Categories.elm | 23 ++++++++++++++++--- src/elm/Shop/Category.elm | 22 +++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 8921c2db3..6647e84e5 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -180,7 +180,7 @@ update msg model loggedIn = , form = Form.init { name = "" - , description = Form.RichText.initModel "new-description-input" Nothing + , description = Form.RichText.initModel (newDescriptionInputId maybeParentId) Nothing } } } @@ -198,7 +198,10 @@ update msg model loggedIn = Open categoryId (Form.init { name = category.name - , description = Form.RichText.initModel "update-category-description" (Just category.description) + , description = + Form.RichText.initModel + ("update-category-description-" ++ Shop.Category.idToString categoryId) + (Just category.description) } ) , actionsDropdown = Nothing @@ -300,7 +303,11 @@ update msg model loggedIn = | newCategoryState = EditingNewCategory { parent = category.parentId - , form = Form.init { name = "", description = Form.RichText.initModel "new-description-input" Nothing } + , form = + Form.init + { name = "" + , description = Form.RichText.initModel (newDescriptionInputId category.parentId) Nothing + } } } |> UR.init @@ -912,6 +919,16 @@ updateDnd loggedIn ext ur = ur +newDescriptionInputId : Maybe Shop.Category.Id -> String +newDescriptionInputId maybeParentId = + case maybeParentId of + Nothing -> + "new-description-input-root" + + Just parentId -> + "new-description-input-" ++ Shop.Category.idToString parentId + + -- VIEW diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 906408bc2..aca17c924 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -1,4 +1,19 @@ -module Shop.Category exposing (Id, Model, Tree, addChild, create, delete, encodeId, idSelectionSet, moveToRoot, selectionSet, treesSelectionSet, update, updateMetadata) +module Shop.Category exposing + ( Id + , Model + , Tree + , addChild + , create + , delete + , encodeId + , idSelectionSet + , idToString + , moveToRoot + , selectionSet + , treesSelectionSet + , update + , updateMetadata + ) import Cambiatus.Mutation import Cambiatus.Object @@ -213,3 +228,8 @@ encodeId (Id id) = unwrapId : Id -> Int unwrapId (Id id) = id + + +idToString : Id -> String +idToString (Id id) = + String.fromInt id From fea9c84571e9dc632b9cbcd5653cd1cb3bad9ede Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 14:45:32 -0300 Subject: [PATCH 048/104] Fix rich text editor bug on safari, fix borders --- .../Page/Community/Settings/Shop/Categories.elm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 6647e84e5..7059be999 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1040,10 +1040,10 @@ view_ translators community model categories = categories ) , viewAddCategory translators - (class "w-full border border-dashed border-transparent" + (class "w-full border border-transparent" :: classList [ ( "bg-green/30", isDraggingSomething ) - , ( "border-black", isDraggingSomething && isDraggingOverAddCategory ) + , ( "!border-black border-dashed", isDraggingSomething && isDraggingOverAddCategory ) ] :: Dnd.dropZone OnRoot GotDndMsg ) @@ -1196,7 +1196,7 @@ viewCategoryWithChildren translators model zipper children = False in div - (class "transition-colors select-none rounded-sm border border-dashed border-transparent" + (class "transition-colors rounded-sm border border-dashed border-transparent" :: classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) , ( "!bg-green/30", isValidDropzone && isDraggingSomething ) @@ -1246,7 +1246,12 @@ viewCategoryWithChildren translators model zipper children = ] -viewAddCategory : Translation.Translators -> List (Html.Attribute Msg) -> Model -> Maybe Shop.Category.Model -> Html Msg +viewAddCategory : + Translation.Translators + -> List (Html.Attribute Msg) + -> Model + -> Maybe Shop.Category.Model + -> Html Msg viewAddCategory translators attrs model maybeParentCategory = let parentId = @@ -1275,7 +1280,7 @@ viewAddCategory translators attrs model maybeParentCategory = EditingNewCategory newCategoryData -> if newCategoryData.parent == parentId then - Form.view (class "bg-white border border-gray-300 rounded-md p-4" :: attrs) + Form.view (class "border !border-gray-300 rounded-md p-4" :: attrs) translators (\submitButton -> [ div [ class "flex flex-col sm:flex-row justify-end gap-4 mt-10" ] From 4b0f9e9dfafda886196cdf45bd0f93839c1a5a54 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 14:48:04 -0300 Subject: [PATCH 049/104] Hide form after creating new category --- src/elm/Page/Community/Settings/Shop/Categories.elm | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 7059be999..14acabd13 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -299,17 +299,7 @@ update msg model loggedIn = _ -> identity in - { model - | newCategoryState = - EditingNewCategory - { parent = category.parentId - , form = - Form.init - { name = "" - , description = Form.RichText.initModel (newDescriptionInputId category.parentId) Nothing - } - } - } + { model | newCategoryState = NotEditing } |> UR.init |> insertInCommunity From 45f337ca79b07430192efd6f2dead91f8653f926 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 15:03:00 -0300 Subject: [PATCH 050/104] Hide new category form if ancestor has been collapsed --- .../Community/Settings/Shop/Categories.elm | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 14acabd13..a833e79e5 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -169,6 +169,27 @@ update msg model loggedIn = else EverySet.insert categoryId model.expandedCategories + , newCategoryState = + case model.newCategoryState of + NotEditing -> + NotEditing + + EditingNewCategory { parent } -> + case parent of + Nothing -> + model.newCategoryState + + Just parentId -> + getCategoryZipper categoryId + |> Maybe.map + (\categoryZipper -> + if isAncestorOf parentId (Tree.Zipper.tree categoryZipper) then + NotEditing + + else + model.newCategoryState + ) + |> Maybe.withDefault model.newCategoryState } |> UR.init From fc8d6c90d630bbe964dddf8635a212b686ed4d4a Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 15:31:40 -0300 Subject: [PATCH 051/104] Add icon to categories --- .../Community/Settings/Shop/Categories.elm | 43 ++++++++++++++++--- src/elm/Shop/Category.elm | 5 ++- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index a833e79e5..09189664d 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -15,6 +15,7 @@ import Dict import Dnd import EverySet exposing (EverySet) import Form +import Form.File import Form.RichText import Form.Text import Form.Validate @@ -218,7 +219,12 @@ update msg model loggedIn = | categoryModalState = Open categoryId (Form.init - { name = category.name + { icon = + Form.File.initSingle + { fileUrl = category.icon + , aspectRatio = Just 1 + } + , name = category.name , description = Form.RichText.initModel ("update-category-description-" ++ Shop.Category.idToString categoryId) @@ -394,7 +400,8 @@ update msg model loggedIn = |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.update (Tree.Zipper.label zipper) - { name = formOutput.name + { icon = formOutput.icon + , name = formOutput.name , slug = formOutput.slug , description = formOutput.description } @@ -1565,6 +1572,7 @@ viewShareCategoryPreview community values = , div [ class "isolate mr-3 z-10 ml-auto w-full sm:w-3/4 md:w-2/3 border border-gray-300 rounded-large relative before:absolute before:bg-white before:border-t before:border-r before:border-gray-300 before:-top-px before:rounded-br-super before:rounded-tr-sm before:-right-2 before:w-8 before:h-4 before:-z-10" ] [ div [ class "bg-white p-1 rounded-large" ] [ div [ class "flex w-full bg-gray-100 rounded-large" ] + -- TODO - Display the category image/icon if it has one [ div [ class "bg-gray-200 p-6 rounded-l-large w-1/4 flex-shrink-0 grid place-items-center" ] [ Icons.image "" ] , div [ class "py-2 mx-4 w-full" ] @@ -1646,13 +1654,15 @@ newCategoryForm translators = type alias UpdateCategoryFormInput = - { name : String + { icon : Form.File.SingleModel + , name : String , description : Form.RichText.Model } type alias UpdateCategoryFormOutput = { id : Shop.Category.Id + , icon : Maybe String , name : String , slug : Slug , description : Markdown @@ -1662,13 +1672,34 @@ type alias UpdateCategoryFormOutput = updateCategoryForm : Translation.Translators -> Shop.Category.Id -> Form.Form msg UpdateCategoryFormInput UpdateCategoryFormOutput updateCategoryForm translators id = Form.succeed - (\{ name, slug } description -> - { name = name + (\icon { name, slug } description -> + { id = id + , icon = icon + , name = name , slug = slug , description = description - , id = id } ) + |> Form.with + (Form.File.init { id = "update-category-icon" } + |> Form.File.withImageClass "object-cover rounded-full w-20 h-20" + |> Form.File.withEntryContainerAttributes (\_ -> [ class "mx-auto rounded-full w-20 h-20" ]) + |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!rounded-full w-20 h-20" ]) + |> Form.File.withAddImagesContainerAttributes [ class "mx-auto w-20 h-20" ] + |> Form.File.withImageCropperAttributes [ class "rounded-full" ] + |> Form.File.withContainerAttributes [ class "mb-10" ] + |> Form.File.withEditIconOverlay + -- TODO - I18N + |> Form.File.withLabel "Icon" + |> Form.file + { parser = Ok + , translators = translators + , value = .icon + , update = \newIcon values -> { values | icon = newIcon } + , externalError = always Nothing + } + |> Form.optional + ) |> Form.with (nameAndSlugForm translators { nameFieldId = "update-category-name" }) |> Form.with -- TODO - I18N diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index aca17c924..727f9e731 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -71,14 +71,15 @@ create { name, slug, description, parentId } = update : Model - -> { name : String, description : Markdown, slug : Slug } + -> { icon : Maybe String, name : String, description : Markdown, slug : Slug } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -update model { name, description, slug } = +update model { icon, name, description, slug } = Cambiatus.Mutation.category (\optionals -> { optionals | id = OptionalArgument.Present (unwrapId model.id) + , iconUri = OptionalArgument.fromMaybeWithNull icon , slug = OptionalArgument.Present (Slug.toString slug) , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) From 4a16b29ee0ad14c94d20d0690d98d5bce8f37933 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 15:53:00 -0300 Subject: [PATCH 052/104] Add category image --- .../Community/Settings/Shop/Categories.elm | 32 +++++++++++++++++-- src/elm/Shop/Category.elm | 5 +-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 09189664d..9609c8f30 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -229,6 +229,11 @@ update msg model loggedIn = Form.RichText.initModel ("update-category-description-" ++ Shop.Category.idToString categoryId) (Just category.description) + , image = + Form.File.initSingle + { fileUrl = category.image + , aspectRatio = Nothing + } } ) , actionsDropdown = Nothing @@ -404,6 +409,7 @@ update msg model loggedIn = , name = formOutput.name , slug = formOutput.slug , description = formOutput.description + , image = formOutput.image } Shop.Category.selectionSet ) @@ -1450,6 +1456,7 @@ viewCategoryModal translators category formModel = -- TODO - I18N |> Modal.withHeader "Editing category" |> Modal.withBody + -- TODO - Add some description - "Edit icon, name, description and image" [ Form.viewWithoutSubmit [ class "mt-2" ] translators (\_ -> []) @@ -1657,6 +1664,7 @@ type alias UpdateCategoryFormInput = { icon : Form.File.SingleModel , name : String , description : Form.RichText.Model + , image : Form.File.SingleModel } @@ -1666,18 +1674,20 @@ type alias UpdateCategoryFormOutput = , name : String , slug : Slug , description : Markdown + , image : Maybe String } updateCategoryForm : Translation.Translators -> Shop.Category.Id -> Form.Form msg UpdateCategoryFormInput UpdateCategoryFormOutput updateCategoryForm translators id = Form.succeed - (\icon { name, slug } description -> + (\icon { name, slug } description image -> { id = id , icon = icon , name = name , slug = slug , description = description + , image = image } ) |> Form.with @@ -1704,7 +1714,7 @@ updateCategoryForm translators id = |> Form.with -- TODO - I18N (Form.RichText.init { label = "Description" } - |> Form.RichText.withContainerAttrs [ class "mb-0" ] + |> Form.RichText.withContainerAttrs [ class "mb-10" ] |> Form.richText { parser = Form.Validate.succeed @@ -1715,6 +1725,24 @@ updateCategoryForm translators id = , externalError = always Nothing } ) + |> Form.with + (Form.File.init { id = "update-category-image" } + -- TODO - I18N + |> Form.File.withLabel "Image" + |> Form.File.withContainerAttributes [ class "w-full" ] + |> Form.File.withAddImagesContainerAttributes [ class "!w-full" ] + |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!w-full min-h-48" ]) + |> Form.File.withImageClass "rounded-md" + |> Form.File.withEditIconOverlay + |> Form.file + { parser = Ok + , translators = translators + , value = .image + , update = \newImage values -> { values | image = newImage } + , externalError = always Nothing + } + |> Form.optional + ) type alias MetadataFormInput = diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 727f9e731..b385c5528 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -71,15 +71,16 @@ create { name, slug, description, parentId } = update : Model - -> { icon : Maybe String, name : String, description : Markdown, slug : Slug } + -> { icon : Maybe String, name : String, description : Markdown, slug : Slug, image : Maybe String } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -update model { icon, name, description, slug } = +update model { icon, name, description, slug, image } = Cambiatus.Mutation.category (\optionals -> { optionals | id = OptionalArgument.Present (unwrapId model.id) , iconUri = OptionalArgument.fromMaybeWithNull icon + , imageUri = OptionalArgument.fromMaybeWithNull image , slug = OptionalArgument.Present (Slug.toString slug) , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) From 59a477d482d2fc5551fd8ae050f6dd0a21680290 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 16:10:54 -0300 Subject: [PATCH 053/104] Show image or icon on sharing preview --- .../Community/Settings/Shop/Categories.elm | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 9609c8f30..cf5754bc1 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -21,8 +21,8 @@ import Form.Text import Form.Validate import Graphql.Http import Graphql.SelectionSet -import Html exposing (Html, button, details, div, li, p, span, summary, text, ul) -import Html.Attributes exposing (class, classList, id, type_) +import Html exposing (Html, button, details, div, img, li, p, span, summary, text, ul) +import Html.Attributes exposing (alt, class, classList, id, src, type_) import Html.Attributes.Aria exposing (ariaHidden) import Html.Events exposing (onClick) import Icons @@ -1504,7 +1504,7 @@ viewCategoryMetadataModal translators community category formModel = , Form.viewWithoutSubmit [ class "mt-2" ] translators (\_ -> []) - (metadataForm translators community category.id) + (metadataForm translators community category) formModel { toMsg = GotMetadataFormMsg } ] @@ -1519,7 +1519,7 @@ viewCategoryMetadataModal translators community category formModel = , button [ class "button button-primary w-full sm:w-40" , onClick - (Form.parse (metadataForm translators community category.id) + (Form.parse (metadataForm translators community category) formModel { onError = GotMetadataFormMsg , onSuccess = SubmittedMetadataForm @@ -1568,8 +1568,8 @@ viewConfirmDeleteCategoryModal categoryId = |> Modal.toHtml -viewShareCategoryPreview : Community.Model -> MetadataFormInput -> Html msg -viewShareCategoryPreview community values = +viewShareCategoryPreview : Community.Model -> Shop.Category.Model -> MetadataFormInput -> Html msg +viewShareCategoryPreview community category values = div [] [ -- TODO - I18N p [ class "label" ] [ text "Preview" ] @@ -1579,9 +1579,18 @@ viewShareCategoryPreview community values = , div [ class "isolate mr-3 z-10 ml-auto w-full sm:w-3/4 md:w-2/3 border border-gray-300 rounded-large relative before:absolute before:bg-white before:border-t before:border-r before:border-gray-300 before:-top-px before:rounded-br-super before:rounded-tr-sm before:-right-2 before:w-8 before:h-4 before:-z-10" ] [ div [ class "bg-white p-1 rounded-large" ] [ div [ class "flex w-full bg-gray-100 rounded-large" ] - -- TODO - Display the category image/icon if it has one - [ div [ class "bg-gray-200 p-6 rounded-l-large w-1/4 flex-shrink-0 grid place-items-center" ] - [ Icons.image "" ] + [ case Maybe.Extra.or category.image category.icon of + Nothing -> + div [ class "bg-gray-200 p-6 rounded-l-large w-1/4 flex-shrink-0 grid place-items-center" ] + [ Icons.image "" ] + + Just previewImage -> + img + [ src previewImage + , alt "" + , class "bg-gray-100 rounded-l-large w-1/4 flex-shrink-0 object-contain" + ] + [] , div [ class "py-2 mx-4 w-full" ] [ if String.isEmpty values.metaTitle then div [ class "w-3/4 bg-gray-300 rounded font-bold" ] @@ -1762,11 +1771,11 @@ type alias MetadataFormOutput = } -metadataForm : Translation.Translators -> Community.Model -> Shop.Category.Id -> Form.Form msg MetadataFormInput MetadataFormOutput -metadataForm translators community categoryId = +metadataForm : Translation.Translators -> Community.Model -> Shop.Category.Model -> Form.Form msg MetadataFormInput MetadataFormOutput +metadataForm translators community category = Form.succeed (\metaTitle metaDescription metaKeywords -> - { id = categoryId + { id = category.id , metaTitle = metaTitle , metaDescription = metaDescription , metaKeywords = metaKeywords @@ -1822,7 +1831,10 @@ metadataForm translators community categoryId = , externalError = always Nothing } ) - |> Form.withNoOutput ((viewShareCategoryPreview community >> Form.arbitrary) |> Form.introspect) + |> Form.withNoOutput + (Form.introspect + (\values -> Form.arbitrary (viewShareCategoryPreview community category values)) + ) nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Slug } From a7ed68961dd5638d24f59164c988e2e8101d906d Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 16:24:24 -0300 Subject: [PATCH 054/104] Adjust action icons sizes --- .../Page/Community/Settings/Shop/Categories.elm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index cf5754bc1..db6334d5f 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1374,7 +1374,7 @@ viewActions model zipper = ] [ li [] [ viewAction [] - { icon = Icons.edit + { icon = Icons.edit "w-4 ml-1 mr-3" -- TODO - I18N , label = "Edit main information" @@ -1383,7 +1383,7 @@ viewActions model zipper = ] , li [] [ viewAction [] - { icon = Icons.edit + { icon = Icons.edit "w-4 ml-1 mr-3" -- TODO - I18N , label = "Edit sharing data" @@ -1393,8 +1393,7 @@ viewActions model zipper = , if canGoUp then li [] [ viewAction [] - -- TODO - Use correct icon - { icon = \classes -> Icons.arrowDown ("rotate-180 " ++ classes) + { icon = Icons.arrowDown "rotate-180 w-6 mr-2" , label = "Move up" , onClickMsg = ClickedMoveUp category.id } @@ -1405,8 +1404,7 @@ viewActions model zipper = , if canGoDown then li [] [ viewAction [] - -- TODO - Use correct icon - { icon = Icons.arrowDown + { icon = Icons.arrowDown "w-6 mr-2" , label = "Move down" , onClickMsg = ClickedMoveDown category.id } @@ -1416,7 +1414,7 @@ viewActions model zipper = text "" , li [] [ viewAction [ class "text-red hover:bg-red/10" ] - { icon = Icons.trash + { icon = Icons.trash "w-4 ml-1 mr-3" -- TODO - I18N , label = "Delete" @@ -1430,7 +1428,7 @@ viewActions model zipper = viewAction : List (Html.Attribute Msg) -> - { icon : String -> Svg Msg + { icon : Svg Msg , label : String , onClickMsg : Msg } @@ -1442,7 +1440,7 @@ viewAction containerAttrs { icon, label, onClickMsg } = :: Utils.onClickNoBubble onClickMsg :: containerAttrs ) - [ icon "w-4 mr-2" + [ icon , text label ] From 91267ae18cd5ec6f6d7461e6430ab296e3f0aabb Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 21 Jun 2022 22:49:29 -0300 Subject: [PATCH 055/104] Add side scrolling with sticky positioning --- .../Community/Settings/Shop/Categories.elm | 71 +++++++++++++------ src/styles/main.css | 4 ++ tailwind.config.js | 15 ++++ 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index db6334d5f..e9b3ae2ce 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -970,10 +970,10 @@ view loggedIn model = [ Page.viewHeader loggedIn title , case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.NotAsked -> - viewLoading + viewLoading model RemoteData.Loading -> - viewLoading + viewLoading model RemoteData.Failure fieldErr -> case fieldErr of @@ -993,17 +993,23 @@ view loggedIn model = } -viewPageContainer : { children : List (Html Msg), modals : List (Html Msg) } -> Html Msg -viewPageContainer { children, modals } = +viewPageContainer : { children : List (Html Msg), modals : List (Html Msg) } -> Model -> Html Msg +viewPageContainer { children, modals } model = div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] - (div [ class "bg-white container mx-auto pt-6 pb-7 px-4 w-full sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" ] + (div + [ class "bg-white container mx-auto pt-6 pb-7 w-full px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" + , classList + [ ( "overflow-x-scroll", Maybe.Extra.isNothing model.actionsDropdown ) + , ( "overflow-x-hidden", Maybe.Extra.isJust model.actionsDropdown ) + ] + ] children :: modals ) -viewLoading : Html Msg -viewLoading = +viewLoading : Model -> Html Msg +viewLoading model = let viewBar : List (Html.Attribute Msg) -> Html Msg viewBar attributes = @@ -1023,6 +1029,7 @@ viewLoading = , viewBar [ class "!mt-6" ] ] } + model view_ : Translation.Translators -> Community.Model -> Model -> List Shop.Category.Tree -> Html Msg @@ -1064,7 +1071,7 @@ view_ translators community model categories = categories ) , viewAddCategory translators - (class "w-full border border-transparent" + (class "w-full border border-transparent sticky left-0" :: classList [ ( "bg-green/30", isDraggingSomething ) , ( "!border-black border-dashed", isDraggingSomething && isDraggingOverAddCategory ) @@ -1105,6 +1112,7 @@ view_ translators community model categories = viewCategoryMetadataModal translators community openCategory formModel ] } + model viewCategoryTree : @@ -1127,7 +1135,7 @@ viewCategoryTree translators model rootZipper currentTree = viewCategory : Shop.Category.Model -> Html Msg viewCategory category = button - [ class "hover:underline" + [ class "hover:underline whitespace-nowrap" , Utils.onClickNoBubble (ClickedCategory category.id) ] [ text category.name @@ -1220,11 +1228,11 @@ viewCategoryWithChildren translators model zipper children = False in div - (class "transition-colors rounded-sm border border-dashed border-transparent" + (class "transition-colors rounded-sm" :: classList [ ( "bg-gray-300 rounded-sm cursor-wait", EverySet.member category.id model.deleting ) , ( "!bg-green/30", isValidDropzone && isDraggingSomething ) - , ( "border-black", isValidDropzone && isDraggingSomething && isDraggingOver ) + , ( "outline-black outline-offset-0", isValidDropzone && isDraggingSomething && isDraggingOver ) ] :: (if isValidDropzone then Dnd.dropZone (OnTopOf category.id) GotDndMsg @@ -1239,7 +1247,7 @@ viewCategoryWithChildren translators model zipper children = else class "" - , class "parent" + , class "parent grand-parent" , classList [ ( "pointer-events-none", EverySet.member category.id model.deleting ) ] ] [ summary @@ -1252,19 +1260,35 @@ viewCategoryWithChildren translators model zipper children = :: onClick (ClickedToggleExpandCategory category.id) :: Dnd.draggable category.id GotDndMsg ) - [ div [ class "flex items-center w-full" ] + [ div [ class "flex items-center sticky left-0 w-full" ] [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) , viewCategory category ] - , viewActions model zipper + , div + [ class "sticky right-0 bg-white rounded-md transition-color" + , classList + [ ( "bg-transparent", isDraggingSomething ) + , ( "z-10", model.actionsDropdown == Just category.id ) + ] + ] + [ viewActions + [ classList + [ ( "!bg-green/20", isParentOfNewCategoryForm ) + , ( "grand-parent-3-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) + , ( "bg-orange-100/20", hasActionsMenuOpen ) + ] + ] + model + zipper + ] ] - , div [ class "ml-4 flex flex-col mb-4 mt-2" ] + , div [ class "ml-4 mb-4 mt-2" ] [ ul [ class "grid gap-y-2" , classList [ ( "mb-2", not (List.isEmpty children) ) ] ] (List.map (\child -> li [] [ child ]) children) - , viewAddCategory translators [] model (Just category) + , viewAddCategory translators [ class "w-full sticky left-0" ] model (Just category) ] ] ] @@ -1331,8 +1355,8 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs -viewActions : Model -> Tree.Zipper.Zipper Shop.Category.Model -> Html Msg -viewActions model zipper = +viewActions : List (Html.Attribute Msg) -> Model -> Tree.Zipper.Zipper Shop.Category.Model -> Html Msg +viewActions attrs model zipper = let category = Tree.Zipper.label zipper @@ -1359,10 +1383,11 @@ viewActions model zipper = in div [ class "relative" ] [ button - [ class "h-8 px-2 rounded-sm transition-colors hover:bg-orange-300/30 active:bg-orange-300/60 action-opener" - , classList [ ( "bg-orange-300/60", isDropdownOpen ) ] - , Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) - ] + (class "h-8 px-2 rounded-sm transition-colors hover:!bg-orange-300/30 active:!bg-orange-300/60 action-opener" + :: classList [ ( "bg-orange-300/60", isDropdownOpen ) ] + :: Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) + :: attrs + ) -- TODO - Use correct icon [ Icons.plus "h-4 pointer-events-none" ] , if not isDropdownOpen then @@ -1370,7 +1395,7 @@ viewActions model zipper = else ul - [ class "absolute z-10 right-0 bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" + [ class "absolute z-50 right-0 bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" ] [ li [] [ viewAction [] diff --git a/src/styles/main.css b/src/styles/main.css index 5e5a442c2..4f871366e 100755 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -455,6 +455,10 @@ https://tailwindcss.com/docs/adding-new-utilities */ width: 0 !important; display: none; } + + .outline-offset-0 { + outline-offset: 0; + } } /* End of extra utilities */ diff --git a/tailwind.config.js b/tailwind.config.js index 5830991c3..ee0b09082 100755 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -279,6 +279,21 @@ module.exports = { ) }) }) + }, + // Create `grand-parent-*` variants. This works similar to `group-*` variants, but + // only works on direct children of the direct children of the `grand-parent` class. + function ({ addVariant, e }) { + const operations = ['hover'] + + operations.forEach((operation) => { + [1, 2, 3, 4, 5].forEach((level) => { + addVariant(`grand-parent-${level}-${operation}`, ({ modifySelectors, separator }) => { + modifySelectors(({ className }) => + `.grand-parent:${operation}>${'*>'.repeat(level)}.grand-parent-${level}-${operation}${e(separator)}${e(className)}` + ) + }) + }) + }) } ], purge: [ From 1b5e72424aca79cd409f5b5ff8a2c8c809162d0a Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 22 Jun 2022 15:08:24 -0300 Subject: [PATCH 056/104] Add empty message, fix add category button sticky --- .../Community/Settings/Shop/Categories.elm | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index e9b3ae2ce..7042980ff 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -21,7 +21,7 @@ import Form.Text import Form.Validate import Graphql.Http import Graphql.SelectionSet -import Html exposing (Html, button, details, div, img, li, p, span, summary, text, ul) +import Html exposing (Html, button, details, div, h2, img, li, p, span, summary, text, ul) import Html.Attributes exposing (alt, class, classList, id, src, type_) import Html.Attributes.Aria exposing (ariaHidden) import Html.Events exposing (onClick) @@ -1053,8 +1053,20 @@ view_ translators community model categories = { children = [ case categories of [] -> - -- TODO - Show something when there are no categories yet - text "" + div [] + [ img + [ src "/images/not_found.svg" + , alt "" + , class "w-2/3 md:w-1/3 mx-auto" + ] + [] + , h2 [ class "w-full md:w-2/3 mx-auto font-bold text-lg text-center mt-4" ] + -- TODO - I18N + [ text "Looks like your community doesn't have any categories!" ] + , p [ class "w-full md:w-2/3 mx-auto text-center mt-2 mb-6" ] + -- TODO - I18N + [ text "Get started by creating a new category below" ] + ] first :: others -> let @@ -1071,10 +1083,10 @@ view_ translators community model categories = categories ) , viewAddCategory translators - (class "w-full border border-transparent sticky left-0" + (class "w-full sticky left-0" :: classList [ ( "bg-green/30", isDraggingSomething ) - , ( "!border-black border-dashed", isDraggingSomething && isDraggingOverAddCategory ) + , ( "outline-black outline-offset-0", isDraggingSomething && isDraggingOverAddCategory ) ] :: Dnd.dropZone OnRoot GotDndMsg ) @@ -1282,13 +1294,15 @@ viewCategoryWithChildren translators model zipper children = zipper ] ] - , div [ class "ml-4 mb-4 mt-2" ] + , div [ class "ml-4 mt-2" ] [ ul [ class "grid gap-y-2" , classList [ ( "mb-2", not (List.isEmpty children) ) ] ] (List.map (\child -> li [] [ child ]) children) - , viewAddCategory translators [ class "w-full sticky left-0" ] model (Just category) + ] + , div [ class "ml-4 mb-4" ] + [ viewAddCategory translators [ class "w-full" ] model (Just category) ] ] ] @@ -1311,15 +1325,17 @@ viewAddCategory translators attrs model maybeParentCategory = :: onClick (ClickedAddCategory parentId) :: customAttrs ) - [ Icons.plus "w-4 h-4 mr-2" - , case maybeParentCategory of - Nothing -> - -- TODO - I18N - text "Add new category" + [ span [ class "sticky left-2 flex items-center" ] + [ Icons.plus "w-4 h-4 mr-2" + , case maybeParentCategory of + Nothing -> + -- TODO - I18N + text "Add new category" - Just { name } -> - -- TODO - I18N - text ("Add sub-category of " ++ name) + Just { name } -> + -- TODO - I18N + text ("Add sub-category of " ++ name) + ] ] in case model.newCategoryState of From 1371072ae8237bd3272d123d4bc3fad919705460 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 22 Jun 2022 16:59:37 -0300 Subject: [PATCH 057/104] Adjust icon and overflows --- src/elm/Icons.elm | 9 ++++++++- src/elm/Page/Community/Settings/Shop/Categories.elm | 9 ++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/elm/Icons.elm b/src/elm/Icons.elm index df1b29406..abccb9846 100644 --- a/src/elm/Icons.elm +++ b/src/elm/Icons.elm @@ -16,6 +16,7 @@ module Icons exposing , coin , coinHeart , edit + , ellipsis , exclamation , externalLink , flag @@ -315,7 +316,7 @@ trash class_ = edit : String -> Svg msg edit class_ = - svg [ fill "currentCOlor", height "26", viewBox "0 0 24 26", width "24", class class_ ] + svg [ fill "currentColor", height "26", viewBox "0 0 24 26", width "24", class class_ ] [ Svg.path [ clipRule "evenodd", d "M20.3091 24.2273H1.84628V1.77273H12.3085V0H1.84628C0.826607 0 0 0.793677 0 1.77273V24.2273C0 25.2063 0.826607 26 1.84628 26H20.3091C21.3287 26 22.1553 25.2063 22.1553 24.2273V13H20.3091V24.2273Z", fill "currentColor", fillRule "evenodd" ] [] , Svg.path [ clipRule "evenodd", d "M23.46 2.20989L22.1553 0.957161C21.4345 0.265907 20.2668 0.265907 19.5459 0.957161C19.5459 0.957161 9.28678 10.7958 9.13292 11.3926L8.67135 13.1653C8.51335 13.7699 8.69759 14.4096 9.15659 14.8503C9.61558 15.2911 10.2819 15.468 10.9115 15.3163L12.7578 14.8731C13.3732 14.7194 23.46 4.72125 23.46 4.72125C24.18 4.02914 24.18 2.90791 23.46 2.2158V2.20989ZM13.4901 11.7826L12.2285 10.5713L12.1854 10.5358L20.8506 2.2158L22.1553 3.46852L13.4901 11.7826Z", fill "currentColor", fillRule "evenodd" ] @@ -323,6 +324,12 @@ edit class_ = ] +ellipsis : String -> Svg msg +ellipsis class_ = + svg [ width "32", height "32", viewBox "0 0 32 32", fill "none", class class_ ] + [ Svg.path [ d "M4 20C6.20934 20 8 18.2089 8 16C8 13.7907 6.20934 12 4 12C1.79066 12 0 13.7907 0 16C0 18.2089 1.79066 20 4 20Z", fill "currentColor" ] [], Svg.path [ d "M28 20C30.2089 20 32 18.2091 32 16C32 13.7909 30.2089 12 28 12C25.7907 12 24 13.7909 24 16C24 18.2091 25.7907 20 28 20Z", fill "currentColor" ] [], Svg.path [ d "M16 20C18.2089 20 20 18.2089 20 16C20 13.7907 18.2089 12 16 12C13.7907 12 12 13.7907 12 16C12 18.2089 13.7907 20 16 20Z", fill "currentColor" ] [] ] + + settings : String -> Svg msg settings class_ = svg [ fill "none", height "28", viewBox "0 0 28 28", width "28", class class_ ] diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 7042980ff..93ae89e13 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -995,12 +995,12 @@ view loggedIn model = viewPageContainer : { children : List (Html Msg), modals : List (Html Msg) } -> Model -> Html Msg viewPageContainer { children, modals } model = - div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20" ] + div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20 overflow-x-hidden" ] (div [ class "bg-white container mx-auto pt-6 pb-7 w-full px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" , classList [ ( "overflow-x-scroll", Maybe.Extra.isNothing model.actionsDropdown ) - , ( "overflow-x-hidden", Maybe.Extra.isJust model.actionsDropdown ) + , ( "overflow-y-visible", Maybe.Extra.isJust model.actionsDropdown ) ] ] children @@ -1404,14 +1404,13 @@ viewActions attrs model zipper = :: Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) :: attrs ) - -- TODO - Use correct icon - [ Icons.plus "h-4 pointer-events-none" ] + [ Icons.ellipsis "h-4 pointer-events-none text-gray-800" ] , if not isDropdownOpen then text "" else ul - [ class "absolute z-50 right-0 bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" + [ class "absolute right-0 bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" ] [ li [] [ viewAction [] From 90de45a1d700afac802c685e848224a55331d8ae Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 22 Jun 2022 17:42:07 -0300 Subject: [PATCH 058/104] Add focus styles and aria attributes --- .../Community/Settings/Shop/Categories.elm | 53 ++++++++++++------- tailwind.config.js | 2 +- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 93ae89e13..80d6429c2 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -9,6 +9,7 @@ module Page.Community.Settings.Shop.Categories exposing ) import Api.Graphql.DeleteStatus +import Browser.Dom import Browser.Events import Community import Dict @@ -23,7 +24,7 @@ import Graphql.Http import Graphql.SelectionSet import Html exposing (Html, button, details, div, h2, img, li, p, span, summary, text, ul) import Html.Attributes exposing (alt, class, classList, id, src, type_) -import Html.Attributes.Aria exposing (ariaHidden) +import Html.Attributes.Aria exposing (ariaHasPopup, ariaHidden, ariaLabel) import Html.Events exposing (onClick) import Icons import Json.Decode @@ -102,7 +103,8 @@ type CategoryFormState formInput type Msg - = PressedEsc + = NoOp + | PressedEsc | ClickedToggleExpandCategory Shop.Category.Id | ClickedAddCategory (Maybe Shop.Category.Id) | ClickedCancelAddCategory @@ -153,6 +155,9 @@ update msg model loggedIn = Nothing in case msg of + NoOp -> + UR.init model + PressedEsc -> { model | newCategoryState = NotEditing @@ -207,6 +212,10 @@ update msg model loggedIn = } } |> UR.init + |> UR.addCmd + (Browser.Dom.focus "new-category-name" + |> Task.attempt (\_ -> NoOp) + ) ClickedCancelAddCategory -> { model | newCategoryState = NotEditing } @@ -995,7 +1004,7 @@ view loggedIn model = viewPageContainer : { children : List (Html Msg), modals : List (Html Msg) } -> Model -> Html Msg viewPageContainer { children, modals } model = - div [ class "container mx-auto sm:px-4 sm:mt-6 sm:mb-20 overflow-x-hidden" ] + div [ class "container mx-auto sm:px-4 sm:mt-6 pb-40 overflow-x-hidden" ] (div [ class "bg-white container mx-auto pt-6 pb-7 w-full px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" , classList @@ -1144,16 +1153,6 @@ viewCategoryTree translators model rootZipper currentTree = currentTree -viewCategory : Shop.Category.Model -> Html Msg -viewCategory category = - button - [ class "hover:underline whitespace-nowrap" - , Utils.onClickNoBubble (ClickedCategory category.id) - ] - [ text category.name - ] - - viewCategoryWithChildren : Translation.Translators -> Model -> Tree.Zipper.Zipper Shop.Category.Model -> List (Html Msg) -> Html Msg viewCategoryWithChildren translators model zipper children = let @@ -1266,7 +1265,7 @@ viewCategoryWithChildren translators model zipper children = (class "marker-hidden flex items-center rounded-sm transition-colors cursor-pointer" :: classList [ ( "!bg-green/20", isParentOfNewCategoryForm ) - , ( "parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) + , ( "focus:bg-orange-100/20 focus-ring parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) , ( "bg-orange-100/20", hasActionsMenuOpen ) ] :: onClick (ClickedToggleExpandCategory category.id) @@ -1274,7 +1273,15 @@ viewCategoryWithChildren translators model zipper children = ) [ div [ class "flex items-center sticky left-0 w-full" ] [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) - , viewCategory category + , button + [ class "hover:underline focus-ring whitespace-nowrap" + , Utils.onClickNoBubble (ClickedCategory category.id) + + -- TODO - I18N + , ariaLabel (category.name ++ " - Click to edit") + ] + [ text category.name + ] ] , div [ class "sticky right-0 bg-white rounded-md transition-color" @@ -1321,7 +1328,8 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton customAttrs = button - (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm whitespace-nowrap" + (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm whitespace-nowrap focus:bg-orange-100/20 focus-ring" + :: type_ "button" :: onClick (ClickedAddCategory parentId) :: customAttrs ) @@ -1399,9 +1407,12 @@ viewActions attrs model zipper = in div [ class "relative" ] [ button - (class "h-8 px-2 rounded-sm transition-colors hover:!bg-orange-300/30 active:!bg-orange-300/60 action-opener" + (class "h-8 px-2 rounded-sm transition-colors hover:!bg-orange-300/30 active:!bg-orange-300/60 action-opener focus-ring focus:bg-orange-300/30" :: classList [ ( "bg-orange-300/60", isDropdownOpen ) ] :: Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) + :: ariaHasPopup "true" + -- TODO - I18N + :: ariaLabel ("Actions for " ++ category.name) :: attrs ) [ Icons.ellipsis "h-4 pointer-events-none text-gray-800" ] @@ -1453,7 +1464,7 @@ viewActions attrs model zipper = else text "" , li [] - [ viewAction [ class "text-red hover:bg-red/10" ] + [ viewAction [ class "text-red hover:bg-red/10 focus:bg-red/10" ] { icon = Icons.trash "w-4 ml-1 mr-3" -- TODO - I18N @@ -1474,9 +1485,8 @@ viewAction : } -> Html Msg viewAction containerAttrs { icon, label, onClickMsg } = - -- TODO - Focus classes button - (class "flex items-center w-full pl-2 pr-8 py-1 rounded-md transition-colors whitespace-nowrap font-bold class hover:bg-gray-200" + (class "flex items-center w-full pl-2 pr-8 py-1 rounded-md transition-colors whitespace-nowrap font-bold class hover:bg-gray-200 focus:bg-gray-200 focus-ring" :: Utils.onClickNoBubble onClickMsg :: containerAttrs ) @@ -2072,6 +2082,9 @@ isAncestorOf childId parentTree = msgToString : Msg -> List String msgToString msg = case msg of + NoOp -> + [ "NoOp" ] + PressedEsc -> [ "PressedEsc" ] diff --git a/tailwind.config.js b/tailwind.config.js index ee0b09082..f7a3784b1 100755 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -270,7 +270,7 @@ module.exports = { // Create `parent-*` variants. This works similar to `group-*` variants, but // only works on direct children of the `parent` class. function ({ addVariant, e }) { - const operations = ['hover'] + const operations = ['hover', 'focus'] operations.forEach((operation) => { addVariant(`parent-${operation}`, ({ modifySelectors, separator }) => { From 613d9974d7c019e1304fbf924fadb12f807610c4 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 22 Jun 2022 22:44:22 -0300 Subject: [PATCH 059/104] Remove coment --- src/elm/Shop.elm | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/elm/Shop.elm b/src/elm/Shop.elm index 1eafa709f..c3a316e7e 100755 --- a/src/elm/Shop.elm +++ b/src/elm/Shop.elm @@ -326,8 +326,6 @@ upsert { id, symbol, title, description, images, price, stockTracking } = Just (Id unwrappedId) -> Present unwrappedId - - -- TODO - Include categories here , categories = Absent , communityId = Present (Eos.symbolToString symbol) , title = Present title From 8f29b6c7b912ff4481144fa7969a5d6a289dd7da Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 22 Jun 2022 22:46:17 -0300 Subject: [PATCH 060/104] Add translations --- public/translations/amh-ETH.json | 52 ++++++ public/translations/cat-CAT.json | 52 ++++++ public/translations/en-US.json | 52 ++++++ public/translations/es-ES.json | 52 ++++++ public/translations/pt-BR.json | 52 ++++++ src/elm/Page/Community/Settings/Settings.elm | 4 +- .../Community/Settings/Shop/Categories.elm | 172 +++++++----------- 7 files changed, 326 insertions(+), 110 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index 0a4577542..1a2afc2de 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -870,6 +870,52 @@ }, "disabled": { "description": "ለማህበረሰቢ ይህ ሱቁ አለተፈቀደም." + }, + "categories": { + "empty": { + "title": "የእርስዎ ማህበረሰብ ምንም አይነት ምድቦች ያለው አይመስልም!", + "description": "ከዚህ በታች አዲስ ምድብ በመፍጠር ይጀምሩ" + }, + "add_root": "አዲስ ምድብ ያክሉ", + "add_child": "የ{{parent_name}} ንዑስ ምድብ አክል", + "click_category_to_edit": "{{category_name}} - ለማርትዕ ጠቅ ያድርጉ", + "actions_for": "እርምጃዎች ለ {{category_name}}", + "fields": { + "name": "ስም", + "slug": "ስሉግ", + "description": "መግለጫ", + "image": "ምስል", + "meta": { + "title": "ርዕስ", + "description": "መግለጫ", + "keywords": "ቁልፍ ቃላት" + } + }, + "form": { + "insert_name": "ስሉግን ለማመንጨት ስም ያስገቡ", + "invalid_slug": "ልክ ያልሆነ ሸርተቴ" + }, + "metadata": { + "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", + "preview": "ቅድመ እይታ", + "preview_text": "ይህ የተጋራው ይዘት ምን እንደሚመስል ግምታዊ ግምት ነው። አገናኙ እየተጋራበት ባለው መድረክ ላይ በመመስረት ሊለወጥ ይችላል።" + }, + "delete": { + "title": "{{category_name}}ን ሰርዝ", + "warning": "ይህን ምድብ ከሰረዙት ሁሉም ንዑስ ክፍሎቹ እንዲሁ እስከመጨረሻው ይሰረዛሉ።", + "confirmation": "እርግጠኛ ነዎት ይህን ምድብ መሰረዝ ይፈልጋሉ?" + }, + "actions": { + "edit_main_information": "ዋና መረጃን ያርትዑ", + "edit_sharing_data": "የማጋራት ውሂብን ያርትዑ", + "move_up": "ወደ ላይ ተንቀሳቀስ", + "move_down": "ወደ ታች ውሰድ", + "delete": "ሰርዝ" + }, + "create_error": "ምድቡን ሲፈጥሩ የሆነ ችግር ተፈጥሯል", + "update_error": "ምድቡን በማዘመን ላይ የሆነ ችግር ተፈጥሯል", + "delete_error": "ምድቡን ሲሰርዝ የሆነ ችግር ተፈጥሯል", + "reorder_error": "ምድቡን እንደገና በማዘዝ ላይ የሆነ ችግር ተፈጥሯል" } }, "dashboard": { @@ -1130,6 +1176,12 @@ "title": "ተጠባቂ ዉጤቶች እና ተግባራት", "description": "" }, + "shop": { + "categories": { + "title": "ምድቦች", + "description": "ሰዎች ለሽያጭ ምን ዓይነት ዕቃዎችን ማስቀመጥ ይችላሉ" + } + }, "contacts": { "title": "የድጋፍ እውቂያዎች", "description": "ኢሜል ፣ WhatsApp ፣ ስልክ ቁጥር ፣ ወዘተ." diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 56f751439..16ddf5e69 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -912,6 +912,52 @@ }, "disabled": { "description": "La botiga està desactivada en aquesta comunitat." + }, + "categories": { + "empty": { + "title": "Sembla que la teva comunitat no té cap categoria!", + "description": "Comenceu creant una categoria nova a continuació" + }, + "add_root": "Afegeix una nova categoria", + "add_child": "Afegeix una subcategoria de {{parent_name}}", + "click_category_to_edit": "{{category_name}} - feu clic per editar", + "actions_for": "Accions per a {{category_name}}", + "fields": { + "name": "Nom", + "slug": "Slug", + "description": "Descripció", + "image": "Imatge", + "meta": { + "title": "Títol", + "description": "Descripció", + "keywords": "Paraules clau" + } + }, + "form": { + "insert_name": "Insereix un nom per generar el slug", + "invalid_slug": "Slug no vàlid" + }, + "metadata": { + "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", + "preview": "Vista prèvia", + "preview_text": "Aquesta és una aproximació de com serà el contingut compartit. Pot canviar en funció de la plataforma en què es comparteix l'enllaç." + }, + "delete": { + "title": "Suprimeix {{category_name}}", + "warning": "Si suprimiu aquesta categoria, totes les seves subcategories també se suprimiran permanentment.", + "confirmation": "Esteu segur que voleu suprimir aquesta categoria?" + }, + "actions": { + "edit_main_information": "Edita la informació principal", + "edit_sharing_data": "Edita les dades per compartir", + "move_up": "Moure cap amunt", + "move_down": "Moure cap avall", + "delete": "Suprimeix" + }, + "create_error": "S'ha produït un error en crear la categoria", + "update_error": "S'ha produït un error en actualitzar la categoria", + "delete_error": "S'ha produït un error en suprimir la categoria", + "reorder_error": "S'ha produït un error en reordenar la categoria" } }, "dashboard": { @@ -1171,6 +1217,12 @@ "title": "Objetius i Ações", "description": "" }, + "shop": { + "categories": { + "title": "Categories", + "description": "Quins tipus d'articles la gent pot posar a la venda" + } + }, "contacts": { "title": "Contactes de suport", "description": "Correu electrònic, whatsapp, número de telèfon, etc." diff --git a/public/translations/en-US.json b/public/translations/en-US.json index 2f3a6509b..3d2cd8efc 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -909,6 +909,52 @@ }, "disabled": { "description": "Shop is disabled in this community." + }, + "categories": { + "empty": { + "title": "Looks like your community doesn't have any categories!", + "description": "Get started by creating a new category below" + }, + "add_root": "Add new category", + "add_child": "Add sub-category of {{parent_name}}", + "click_category_to_edit": "{{category_name}} - Click to edit", + "actions_for": "Actions for {{category_name}}", + "fields": { + "name": "Name", + "slug": "Slug", + "description": "Description", + "image": "Image", + "meta": { + "title": "Title", + "description": "Description", + "keywords": "Keywords" + } + }, + "form": { + "insert_name": "Insert a name to generate the slug", + "invalid_slug": "Invalid slug" + }, + "metadata": { + "guidance": "This information will be used to display rich links when sharing this category.", + "preview": "Preview", + "preview_text": "This is an approximation of what the shared content will look like. It might change depending on the platform the link is being shared on." + }, + "delete": { + "title": "Delete {{category_name}}", + "warning": "If you delete this category, all of its sub-categories will also be permanently deleted.", + "confirmation": "Are you sure you want to delete this category?" + }, + "actions": { + "edit_main_information": "Edit main information", + "edit_sharing_data": "Edit sharing data", + "move_up": "Move up", + "move_down": "Move down", + "delete": "Delete" + }, + "create_error": "Something went wrong when creating the category", + "update_error": "Something went wrong when updating the category", + "delete_error": "Something went wrong when deleting the category", + "reorder_error": "Something went wrong when reordering the category" } }, "dashboard": { @@ -1169,6 +1215,12 @@ "title": "Objectives and Actions", "description": "" }, + "shop": { + "categories": { + "title": "Categories", + "description": "What kinds of items people can put for sale" + } + }, "contacts": { "title": "Support contacts", "description": "Email, whatsapp, phone number, etc." diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index b8b7aaf2d..d94fdb51a 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -912,6 +912,52 @@ }, "disabled": { "description": "La tienda está deshabilitada en esta comunidad." + }, + "categories": { + "empty": { + "title": "¡Parece que tu comunidad no tiene ninguna categoría!", + "description": "Comience creando una nueva categoría abajo" + }, + "add_root": "Añadir nueva categoria", + "add_child": "Añadir subcategoría de {{parent_name}}", + "click_category_to_edit": "{{category_name}} - Haz clic para editar", + "actions_for": "Acciones para {{category_name}}", + "fields": { + "name": "Nombre", + "slug": "Slug", + "description": "Descripción", + "image": "Imagen", + "meta": { + "title": "Título", + "description": "Descripción", + "keywords": "Palabras clave" + } + }, + "form": { + "insert_name": "Inserta un nombre para generar el slug", + "invalid_slug": "Slug no válida" + }, + "metadata": { + "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", + "preview": "Avance", + "preview_text": "Esta es una aproximación de cómo se verá el contenido compartido. Puede cambiar según la plataforma en la que se comparte el enlace." + }, + "delete": { + "title": "Eliminar {{category_name}}", + "warning": "Si elimina esta categoría, todas sus subcategorías también se eliminarán de forma permanente.", + "confirmation": "¿Está seguro de que desea eliminar esta categoría?" + }, + "actions": { + "edit_main_information": "Editar información principal", + "edit_sharing_data": "Editar dados compartidos", + "move_up": "Mover hacia arriba", + "move_down": "Mover hacia abajo", + "delete": "Eliminar" + }, + "create_error": "Algo salió mal al crear la categoría", + "update_error": "Algo salió mal al actualizar la categoría", + "delete_error": "Algo salió mal al eliminar la categoría", + "reorder_error": "Algo salió mal al reordenar la categoría" } }, "dashboard": { @@ -1172,6 +1218,12 @@ "title": "Objetivos y acciones", "description": "" }, + "shop": { + "categories": { + "title": "Categorías", + "description": "Qué tipo de artículos la gente puede poner a la venta" + } + }, "contacts": { "title": "Contactos de soporte", "description": "Correo electrónico, whatsapp, número de teléfono, etc." diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index 2a0a46f88..09cb2e664 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -916,6 +916,52 @@ }, "disabled": { "description": "A loja está desativada nesta comunidade." + }, + "categories": { + "empty": { + "title": "Parece que sua comunidade ainda não tem nenhuma categoria!", + "description": "Comece criando uma nova categoria abaixo" + }, + "add_root": "Adicionar nova categoria", + "add_child": "Adicionar nova sub-categoria de {{parent_name}}", + "click_category_to_edit": "{{category_name}} - Clique para editar", + "actions_for": "Ações para {{category_name}}", + "fields": { + "name": "Nome", + "slug": "Slug", + "description": "Descrição", + "image": "Imagem", + "meta": { + "title": "Título", + "description": "Descrição", + "keywords": "Palavras-chave" + } + }, + "form": { + "insert_name": "Insira um nome para gerar o slug", + "invalid_slug": "Slug inválido" + }, + "metadata": { + "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", + "preview": "Pré-visualização", + "preview_text": "Essa é uma aproximação de como o conteúdo compartilhado será exibido. Isso pode mudar dependendo da plataforma em que o link está sendo compartilhado." + }, + "delete": { + "title": "Deletar {{category_name}}", + "warning": "Se você deletar esta categoria, todas as suas sub-categorias também serão deletadas permanentemente.", + "confirmation": "Tem certeza de que deseja deletar esta categoria?" + }, + "actions": { + "edit_main_information": "Editar informações principais", + "edit_sharing_data": "Editar dados de compartilhamento", + "move_up": "Mover para cima", + "move_down": "Mover para baixo", + "delete": "Deletar" + }, + "create_error": "Algo de errado aconteceu ao criar a categoria", + "update_error": "Algo de errado aconteceu ao editar a categoria", + "delete_error": "Algo de errado aconteceu ao deletar a categoria", + "reorder_error": "Algo de errado aconteceu ao reordenar a categoria" } }, "dashboard": { @@ -1175,6 +1221,12 @@ "title": "Objetivos e Ações", "description": "" }, + "shop": { + "categories": { + "title": "Categorias", + "description": "Que tipos de itens as pessoas podem colocar à venda" + } + }, "contacts": { "title": "Contatos para suporte", "description": "Email, whatsapp, número de telefone, etc." diff --git a/src/elm/Page/Community/Settings/Settings.elm b/src/elm/Page/Community/Settings/Settings.elm index d08c0fbf8..5de1bc2ae 100644 --- a/src/elm/Page/Community/Settings/Settings.elm +++ b/src/elm/Page/Community/Settings/Settings.elm @@ -103,9 +103,7 @@ viewSettingsList shared community = else text "" - - -- TODO - I18N - , settingCard "Shop categories" (t "menu.edit") "What kinds of items people can put for sale" Route.CommunitySettingsShopCategories + , settingCard (t "settings.shop.categories.title") (t "menu.edit") (t "settings.shop.categories.description") Route.CommunitySettingsShopCategories , settingCard (t "settings.contacts.title") (t "menu.edit") (t "settings.contacts.description") Route.CommunitySettingsContacts , settingCard (t "settings.features.title") (t "menu.edit") (t "settings.features.description") Route.CommunitySettingsFeatures , if Maybe.Extra.isJust community.contributionConfiguration then diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 80d6429c2..c34c09883 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -145,6 +145,9 @@ type alias UpdateResult = update : Msg -> Model -> LoggedIn.Model -> UpdateResult update msg model loggedIn = let + { t } = + loggedIn.shared.translators + getCategoryZipper : Shop.Category.Id -> Maybe (Tree.Zipper.Zipper Shop.Category.Model) getCategoryZipper categoryId = case Community.getField loggedIn.selectedCommunity .shopCategories of @@ -356,8 +359,7 @@ update msg model loggedIn = |> EditingNewCategory } |> UR.init - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.create_error")) |> UR.logImpossible msg "Got Nothing after creating category" (Just loggedIn.accountName) @@ -376,8 +378,7 @@ update msg model loggedIn = |> EditingNewCategory } |> UR.init - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.create_error")) FinishedCreatingCategory _ -> UR.init model @@ -451,8 +452,7 @@ update msg model loggedIn = FinishedUpdatingCategory (RemoteData.Success Nothing) -> { model | categoryModalState = Closed } |> UR.init - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.update_error")) |> UR.logImpossible msg "Got Nothing after updating category" (Just loggedIn.accountName) @@ -462,8 +462,7 @@ update msg model loggedIn = FinishedUpdatingCategory (RemoteData.Failure err) -> { model | categoryModalState = Closed } |> UR.init - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Aww :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.update_error")) |> UR.logGraphqlError msg (Just loggedIn.accountName) "Got an error when updating category" @@ -547,8 +546,7 @@ update msg model loggedIn = CompletedDeletingCategory categoryId (RemoteData.Success (Api.Graphql.DeleteStatus.Error reason)) -> { model | deleting = EverySet.remove categoryId model.deleting } |> UR.init - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "error :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.delete_error")) |> UR.logDeletionStatusError msg (Just loggedIn.accountName) { moduleName = "Page.Community.Settings.Shop.Categories" @@ -609,6 +607,7 @@ update msg model loggedIn = | categoryMetadataModalState = Open categoryId (Form.init + -- TODO - Use category title and description { metaTitle = Maybe.withDefault "" category.metaTitle , metaDescription = Maybe.withDefault "" category.metaDescription , metaKeywords = Maybe.withDefault "" category.metaKeywords @@ -762,8 +761,7 @@ update msg model loggedIn = } ] err - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Something went wrong :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.reorder_error")) CompletedMovingCategory _ _ -> UR.init model @@ -824,8 +822,7 @@ update msg model loggedIn = } ] err - -- TODO - I18N - |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure "Something went wrong :(") + |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.reorder_error")) CompletedMovingCategoryToRoot _ _ -> UR.init model @@ -970,8 +967,7 @@ view : LoggedIn.Model -> Model -> { title : String, content : Html Msg } view loggedIn model = let title = - -- TODO - I18N - "TODO" + loggedIn.shared.translators.t "settings.shop.categories.title" in { title = title , content = @@ -1070,11 +1066,9 @@ view_ translators community model categories = ] [] , h2 [ class "w-full md:w-2/3 mx-auto font-bold text-lg text-center mt-4" ] - -- TODO - I18N - [ text "Looks like your community doesn't have any categories!" ] + [ text <| translators.t "shop.categories.empty.description" ] , p [ class "w-full md:w-2/3 mx-auto text-center mt-2 mb-6" ] - -- TODO - I18N - [ text "Get started by creating a new category below" ] + [ text <| translators.t "shop.categories.empty.description" ] ] first :: others -> @@ -1119,7 +1113,12 @@ view_ translators community model categories = text "" Just categoryId -> - viewConfirmDeleteCategoryModal categoryId + case findInTrees (\category -> category.id == categoryId) categories of + Nothing -> + text "" + + Just openCategory -> + viewConfirmDeleteCategoryModal translators openCategory , case model.categoryMetadataModalState of Closed -> text "" @@ -1276,9 +1275,7 @@ viewCategoryWithChildren translators model zipper children = , button [ class "hover:underline focus-ring whitespace-nowrap" , Utils.onClickNoBubble (ClickedCategory category.id) - - -- TODO - I18N - , ariaLabel (category.name ++ " - Click to edit") + , ariaLabel <| translators.tr "shop.categories.click_category_to_edit" [ ( "category_name", category.name ) ] ] [ text category.name ] @@ -1290,7 +1287,7 @@ viewCategoryWithChildren translators model zipper children = , ( "z-10", model.actionsDropdown == Just category.id ) ] ] - [ viewActions + [ viewActions translators [ classList [ ( "!bg-green/20", isParentOfNewCategoryForm ) , ( "grand-parent-3-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) @@ -1337,12 +1334,10 @@ viewAddCategory translators attrs model maybeParentCategory = [ Icons.plus "w-4 h-4 mr-2" , case maybeParentCategory of Nothing -> - -- TODO - I18N - text "Add new category" + text <| translators.t "shop.categories.add_root" Just { name } -> - -- TODO - I18N - text ("Add sub-category of " ++ name) + text <| translators.tr "shop.categories.add_child" [ ( "parent_name", name ) ] ] ] in @@ -1361,11 +1356,9 @@ viewAddCategory translators attrs model maybeParentCategory = , type_ "button" , onClick ClickedCancelAddCategory ] - -- TODO - I18N - [ text "Cancel" ] + [ text <| translators.t "menu.cancel" ] , submitButton [ class "button button-primary w-full sm:w-40" ] - -- TODO - I18N - [ text "Create" ] + [ text <| translators.t "menu.create" ] ] ] ) @@ -1379,8 +1372,8 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs -viewActions : List (Html.Attribute Msg) -> Model -> Tree.Zipper.Zipper Shop.Category.Model -> Html Msg -viewActions attrs model zipper = +viewActions : Translation.Translators -> List (Html.Attribute Msg) -> Model -> Tree.Zipper.Zipper Shop.Category.Model -> Html Msg +viewActions translators attrs model zipper = let category = Tree.Zipper.label zipper @@ -1411,8 +1404,7 @@ viewActions attrs model zipper = :: classList [ ( "bg-orange-300/60", isDropdownOpen ) ] :: Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) :: ariaHasPopup "true" - -- TODO - I18N - :: ariaLabel ("Actions for " ++ category.name) + :: ariaLabel (translators.tr "shop.categories.action_for" [ ( "category_name", category.name ) ]) :: attrs ) [ Icons.ellipsis "h-4 pointer-events-none text-gray-800" ] @@ -1426,18 +1418,14 @@ viewActions attrs model zipper = [ li [] [ viewAction [] { icon = Icons.edit "w-4 ml-1 mr-3" - - -- TODO - I18N - , label = "Edit main information" + , label = translators.t "shop.categories.actions.edit_main_information" , onClickMsg = ClickedCategory category.id } ] , li [] [ viewAction [] { icon = Icons.edit "w-4 ml-1 mr-3" - - -- TODO - I18N - , label = "Edit sharing data" + , label = translators.t "shop.categories.actions.edit_sharing_data" , onClickMsg = ClickedOpenMetadataModal category.id } ] @@ -1445,7 +1433,7 @@ viewActions attrs model zipper = li [] [ viewAction [] { icon = Icons.arrowDown "rotate-180 w-6 mr-2" - , label = "Move up" + , label = translators.t "shop.categories.actions.move_up" , onClickMsg = ClickedMoveUp category.id } ] @@ -1456,7 +1444,7 @@ viewActions attrs model zipper = li [] [ viewAction [] { icon = Icons.arrowDown "w-6 mr-2" - , label = "Move down" + , label = translators.t "shop.categories.actions.move_down" , onClickMsg = ClickedMoveDown category.id } ] @@ -1466,9 +1454,7 @@ viewActions attrs model zipper = , li [] [ viewAction [ class "text-red hover:bg-red/10 focus:bg-red/10" ] { icon = Icons.trash "w-4 ml-1 mr-3" - - -- TODO - I18N - , label = "Delete" + , label = translators.t "shop.categories.actions.delete" , onClickMsg = ClickedDeleteCategory category.id } ] @@ -1501,10 +1487,7 @@ viewCategoryModal translators category formModel = { isVisible = True , closeMsg = ClosedCategoryModal } - -- TODO - I18N - |> Modal.withHeader "Editing category" |> Modal.withBody - -- TODO - Add some description - "Edit icon, name, description and image" [ Form.viewWithoutSubmit [ class "mt-2" ] translators (\_ -> []) @@ -1518,8 +1501,7 @@ viewCategoryModal translators category formModel = [ class "button button-secondary w-full sm:w-40" , onClick ClosedCategoryModal ] - -- TODO - I18N - [ text "Cancel" ] + [ text <| translators.t "menu.cancel" ] , button [ class "button button-primary w-full sm:w-40" , onClick @@ -1530,8 +1512,7 @@ viewCategoryModal translators category formModel = } ) ] - -- TODO - I18N - [ text "Save" ] + [ text <| translators.t "menu.save" ] ] ] |> Modal.withSize Modal.FullScreen @@ -1547,8 +1528,7 @@ viewCategoryMetadataModal translators community category formModel = |> Modal.withHeader "Editing category sharing data" |> Modal.withBody [ p [ class "mb-6" ] - -- TODO - I18N - [ text "This information will be used to display rich links when sharing links to this category" ] + [ text <| translators.t "shop.categories.metadata.guidance" ] , Form.viewWithoutSubmit [ class "mt-2" ] translators (\_ -> []) @@ -1562,8 +1542,7 @@ viewCategoryMetadataModal translators community category formModel = [ class "button button-secondary w-full sm:w-40" , onClick ClosedMetadataModal ] - -- TODO - I18N - [ text "Cancel" ] + [ text <| translators.t "menu.cancel" ] , button [ class "button button-primary w-full sm:w-40" , onClick @@ -1574,28 +1553,23 @@ viewCategoryMetadataModal translators community category formModel = } ) ] - -- TODO - I18N - [ text "Save" ] + [ text <| translators.t "menu.save" ] ] ] |> Modal.withSize Modal.FullScreen |> Modal.toHtml -viewConfirmDeleteCategoryModal : Shop.Category.Id -> Html Msg -viewConfirmDeleteCategoryModal categoryId = +viewConfirmDeleteCategoryModal : Translation.Translators -> Shop.Category.Model -> Html Msg +viewConfirmDeleteCategoryModal translators category = Modal.initWith { isVisible = True , closeMsg = ClosedConfirmDeleteModal } - -- TODO - I18N - Include category name - |> Modal.withHeader "Delete category" + |> Modal.withHeader (translators.tr "shop.categories.delete.title" [ ( "category_name", category.name ) ]) |> Modal.withBody - -- TODO - I18N - [ p [] [ text "If you delete this category, all of its sub-categories will also be permanently deleted." ] - - -- TODO - I18N - , p [] [ text "Are you sure you want to delete this category?" ] + [ p [] [ text <| translators.t "shop.categories.delete.warning" ] + , p [] [ text <| translators.t "shop.categories.delete.confirmation" ] ] |> Modal.withFooter [ div [ class "flex flex-col sm:flex-row gap-4" ] @@ -1603,27 +1577,24 @@ viewConfirmDeleteCategoryModal categoryId = [ class "button button-secondary w-full sm:w-40" , onClick ClosedConfirmDeleteModal ] - -- TODO - I18N - [ text "Cancel" ] + [ text <| translators.t "menu.cancel" ] , button [ class "button button-danger w-full sm:w-40" - , onClick (ConfirmedDeleteCategory categoryId) + , onClick (ConfirmedDeleteCategory category.id) ] - -- TODO - I18N - [ text "Delete" ] + [ text <| translators.t "shop.delete" ] ] ] |> Modal.toHtml -viewShareCategoryPreview : Community.Model -> Shop.Category.Model -> MetadataFormInput -> Html msg -viewShareCategoryPreview community category values = +viewShareCategoryPreview : Translation.Translators -> Community.Model -> Shop.Category.Model -> MetadataFormInput -> Html msg +viewShareCategoryPreview translators community category values = div [] - [ -- TODO - I18N - p [ class "label" ] [ text "Preview" ] - - -- TODO - I18N - , p [ class "mb-4" ] [ text "This is an aproximation of what the shared content will look like. It will change depending on the platform the link is being shared on." ] + [ p [ class "label" ] + [ text <| translators.t "shop.categories.metadata.preview" ] + , p [ class "mb-4" ] + [ text <| translators.t "shop.categories.metadata.preview_text" ] , div [ class "isolate mr-3 z-10 ml-auto w-full sm:w-3/4 md:w-2/3 border border-gray-300 rounded-large relative before:absolute before:bg-white before:border-t before:border-r before:border-gray-300 before:-top-px before:rounded-br-super before:rounded-tr-sm before:-right-2 before:w-8 before:h-4 before:-z-10" ] [ div [ class "bg-white p-1 rounded-large" ] [ div [ class "flex w-full bg-gray-100 rounded-large" ] @@ -1703,8 +1674,7 @@ newCategoryForm translators = ) |> Form.with (nameAndSlugForm translators { nameFieldId = "new-category-name" }) |> Form.with - -- TODO - I18N - (Form.RichText.init { label = "Description" } + (Form.RichText.init { label = translators.t "shop.categories.fields.description" } |> Form.richText { parser = Form.Validate.succeed @@ -1756,8 +1726,6 @@ updateCategoryForm translators id = |> Form.File.withImageCropperAttributes [ class "rounded-full" ] |> Form.File.withContainerAttributes [ class "mb-10" ] |> Form.File.withEditIconOverlay - -- TODO - I18N - |> Form.File.withLabel "Icon" |> Form.file { parser = Ok , translators = translators @@ -1769,8 +1737,7 @@ updateCategoryForm translators id = ) |> Form.with (nameAndSlugForm translators { nameFieldId = "update-category-name" }) |> Form.with - -- TODO - I18N - (Form.RichText.init { label = "Description" } + (Form.RichText.init { label = translators.t "shop.categories.fields.description" } |> Form.RichText.withContainerAttrs [ class "mb-10" ] |> Form.richText { parser = @@ -1784,8 +1751,7 @@ updateCategoryForm translators id = ) |> Form.with (Form.File.init { id = "update-category-image" } - -- TODO - I18N - |> Form.File.withLabel "Image" + |> Form.File.withLabel (translators.t "shop.categories.fields.image") |> Form.File.withContainerAttributes [ class "w-full" ] |> Form.File.withAddImagesContainerAttributes [ class "!w-full" ] |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!w-full min-h-48" ]) @@ -1831,8 +1797,7 @@ metadataForm translators community category = ) |> Form.with (Form.Text.init - -- TODO - I18N - { label = "Title" + { label = translators.t "shop.categories.fields.meta.title" , id = "meta-title-input" } |> Form.textField @@ -1848,8 +1813,7 @@ metadataForm translators community category = ) |> Form.with (Form.Text.init - -- TODO - I18N - { label = "Description" + { label = translators.t "shop.categories.fields.meta.description" , id = "meta-description-input" } |> Form.textField @@ -1865,8 +1829,7 @@ metadataForm translators community category = ) |> Form.with (Form.Text.init - -- TODO - I18N - { label = "Keywords" + { label = translators.t "shop.categories.meta.keywords" , id = "meta-keywords-input" } |> Form.textField @@ -1881,7 +1844,7 @@ metadataForm translators community category = ) |> Form.withNoOutput (Form.introspect - (\values -> Form.arbitrary (viewShareCategoryPreview community category values)) + (\values -> Form.arbitrary (viewShareCategoryPreview translators community category values)) ) @@ -1890,8 +1853,7 @@ nameAndSlugForm translators { nameFieldId } = Form.succeed (\name slug -> { name = name, slug = slug }) |> Form.with (Form.Text.init - -- TODO - I18N - { label = "Name" + { label = translators.t "shop.categories.fields.name" , id = nameFieldId } |> Form.Text.withContainerAttrs [ class "mb-4" ] @@ -1927,17 +1889,14 @@ nameAndSlugForm translators { nameFieldId } = Form.arbitrary (div [ class "mb-10" ] [ View.Components.label [] - -- TODO - I18N - { targetId = nameFieldId, labelText = "Slug" } + { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } , if String.isEmpty name then span [ class "text-gray-400 italic" ] - -- TODO - I18N - [ text "Insert a name to generate the slug" ] + [ text <| translators.t "shop.categories.form.insert_name" ] else span [ class "form-error" ] - -- TODO - I18N - [ text "Invalid slug" ] + [ text <| translators.t "shop.categories.form.invalid_slug" ] ] ) @@ -1945,8 +1904,7 @@ nameAndSlugForm translators { nameFieldId } = Form.arbitraryWith slug (div [ class "mb-10" ] [ View.Components.label [] - -- TODO - I18N - { targetId = nameFieldId, labelText = "Slug" } + { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } -- TODO - We should show a preview of the url, like: -- TODO - "Your url will look like muda.cambiatus.io/shop/categories/organicos--1234" From 504e7faa405e024cfc891f261ca181f01f7f87e4 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 22 Jun 2022 22:50:06 -0300 Subject: [PATCH 061/104] Fix translation, use defaults on metadata, always ask for confirmation before deleting --- .../Community/Settings/Shop/Categories.elm | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index c34c09883..3099b308e 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -476,27 +476,8 @@ update msg model loggedIn = UR.init model ClickedDeleteCategory categoryId -> - case getCategoryZipper categoryId of - Nothing -> - model - |> UR.init - - Just zipper -> - if List.isEmpty (Tree.Zipper.children zipper) then - { model | deleting = EverySet.insert categoryId model.deleting } - |> UR.init - |> UR.addExt - (LoggedIn.mutation - loggedIn - (Api.Graphql.DeleteStatus.selectionSet - (Shop.Category.delete categoryId) - ) - (CompletedDeletingCategory categoryId) - ) - - else - { model | askingForDeleteConfirmation = Just categoryId } - |> UR.init + { model | askingForDeleteConfirmation = Just categoryId } + |> UR.init ClosedConfirmDeleteModal -> { model | askingForDeleteConfirmation = Nothing } @@ -607,9 +588,8 @@ update msg model loggedIn = | categoryMetadataModalState = Open categoryId (Form.init - -- TODO - Use category title and description - { metaTitle = Maybe.withDefault "" category.metaTitle - , metaDescription = Maybe.withDefault "" category.metaDescription + { metaTitle = Maybe.withDefault category.name category.metaTitle + , metaDescription = Maybe.withDefault (Markdown.toUnformattedString category.description) category.metaDescription , metaKeywords = Maybe.withDefault "" category.metaKeywords } ) @@ -1829,7 +1809,7 @@ metadataForm translators community category = ) |> Form.with (Form.Text.init - { label = translators.t "shop.categories.meta.keywords" + { label = translators.t "shop.categories.fields.meta.keywords" , id = "meta-keywords-input" } |> Form.textField From 038bde8d89843117de1465fd2af053bc48820723 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 14:21:34 -0300 Subject: [PATCH 062/104] Accept empty slugs --- public/translations/amh-ETH.json | 2 +- public/translations/cat-CAT.json | 2 +- public/translations/en-US.json | 2 +- public/translations/es-ES.json | 2 +- public/translations/pt-BR.json | 2 +- .../Community/Settings/Shop/Categories.elm | 65 +++++++------------ src/elm/Shop/Category.elm | 14 ++-- 7 files changed, 37 insertions(+), 52 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index 1a2afc2de..b76cca840 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -893,7 +893,7 @@ }, "form": { "insert_name": "ስሉግን ለማመንጨት ስም ያስገቡ", - "invalid_slug": "ልክ ያልሆነ ሸርተቴ" + "empty_slug": "ምንም ዝቃጭ አይፈጠርም" }, "metadata": { "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 16ddf5e69..2ae6326ed 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -935,7 +935,7 @@ }, "form": { "insert_name": "Insereix un nom per generar el slug", - "invalid_slug": "Slug no vàlid" + "empty_slug": "No es generarà cap slug" }, "metadata": { "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", diff --git a/public/translations/en-US.json b/public/translations/en-US.json index 3d2cd8efc..ab75ebd92 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -932,7 +932,7 @@ }, "form": { "insert_name": "Insert a name to generate the slug", - "invalid_slug": "Invalid slug" + "empty_slug": "No slug will be generated" }, "metadata": { "guidance": "This information will be used to display rich links when sharing this category.", diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index d94fdb51a..cf29cbe5c 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -935,7 +935,7 @@ }, "form": { "insert_name": "Inserta un nombre para generar el slug", - "invalid_slug": "Slug no válida" + "empty_slug": "No se generará slug" }, "metadata": { "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index 09cb2e664..d188e9f3a 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -939,7 +939,7 @@ }, "form": { "insert_name": "Insira um nome para gerar o slug", - "invalid_slug": "Slug inválido" + "empty_slug": "Nenhum slug será gerado" }, "metadata": { "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 3099b308e..a35fb78e1 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1641,7 +1641,7 @@ type alias NewCategoryFormInput = type alias NewCategoryFormOutput = { name : String - , slug : Slug + , slug : Maybe Slug , description : Markdown } @@ -1679,7 +1679,7 @@ type alias UpdateCategoryFormOutput = { id : Shop.Category.Id , icon : Maybe String , name : String - , slug : Slug + , slug : Maybe Slug , description : Markdown , image : Maybe String } @@ -1828,7 +1828,7 @@ metadataForm translators community category = ) -nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Slug } +nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Maybe Slug } nameAndSlugForm translators { nameFieldId } = Form.succeed (\name slug -> { name = name, slug = slug }) |> Form.with @@ -1841,21 +1841,6 @@ nameAndSlugForm translators { nameFieldId } = { parser = Form.Validate.succeed >> Form.Validate.stringLongerThan 2 - >> Form.Validate.custom - (\name -> - case Slug.generate name of - Nothing -> - -- Errors are shown below, on the slug field - Err (\_ -> "") - - Just slug -> - if String.length (Slug.toString slug) < 2 then - -- Errors are shown below, on the slug field - Err (\_ -> "") - - else - Ok name - ) >> Form.Validate.validate translators , value = .name , update = \newName values -> { values | name = newName } @@ -1864,33 +1849,27 @@ nameAndSlugForm translators { nameFieldId } = ) |> Form.with ((\{ name } -> - case Slug.generate name of - Nothing -> - Form.arbitrary - (div [ class "mb-10" ] - [ View.Components.label [] - { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } - , if String.isEmpty name then - span [ class "text-gray-400 italic" ] - [ text <| translators.t "shop.categories.form.insert_name" ] - - else - span [ class "form-error" ] - [ text <| translators.t "shop.categories.form.invalid_slug" ] - ] - ) - - Just slug -> - Form.arbitraryWith slug - (div [ class "mb-10" ] - [ View.Components.label [] - { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } - + Form.arbitraryWith (Slug.generate name) + (div [ class "mb-10" ] + [ View.Components.label [] + { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } + , case Slug.generate name of + Just slug -> -- TODO - We should show a preview of the url, like: -- TODO - "Your url will look like muda.cambiatus.io/shop/categories/organicos--1234" - , text (Slug.toString slug) - ] - ) + text (Slug.toString slug) + + Nothing -> + span [ class "text-gray-400 italic" ] + [ if String.isEmpty name then + text <| translators.t "shop.categories.form.insert_name" + + else + -- TODO - I18N + text <| translators.t "shop.categories.form.empty_slug" + ] + ] + ) ) |> Form.introspect ) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index b385c5528..c2e8928f6 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -49,7 +49,7 @@ type alias Model = create : { name : String , description : Markdown - , slug : Slug + , slug : Maybe Slug , parentId : Maybe Id } -> SelectionSet decodesTo Cambiatus.Object.Category @@ -62,7 +62,10 @@ create { name, slug, description, parentId } = parentId |> Maybe.map (\(Id id) -> id) |> OptionalArgument.fromMaybe - , slug = OptionalArgument.Present (Slug.toString slug) + , slug = + slug + |> Maybe.map Slug.toString + |> OptionalArgument.fromMaybeWithNull , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) } @@ -71,7 +74,7 @@ create { name, slug, description, parentId } = update : Model - -> { icon : Maybe String, name : String, description : Markdown, slug : Slug, image : Maybe String } + -> { icon : Maybe String, name : String, description : Markdown, slug : Maybe Slug, image : Maybe String } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation update model { icon, name, description, slug, image } = @@ -81,7 +84,10 @@ update model { icon, name, description, slug, image } = | id = OptionalArgument.Present (unwrapId model.id) , iconUri = OptionalArgument.fromMaybeWithNull icon , imageUri = OptionalArgument.fromMaybeWithNull image - , slug = OptionalArgument.Present (Slug.toString slug) + , slug = + slug + |> Maybe.map Slug.toString + |> OptionalArgument.fromMaybeWithNull , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) } From 488c733cd1ad9dc31b96557da0a7a8635287eec2 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 14:32:52 -0300 Subject: [PATCH 063/104] Disable modal buttons when saving form --- src/elm/Form.elm | 15 +++++++++++++++ .../Page/Community/Settings/Shop/Categories.elm | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/elm/Form.elm b/src/elm/Form.elm index e713236b1..695c2d404 100644 --- a/src/elm/Form.elm +++ b/src/elm/Form.elm @@ -5,6 +5,7 @@ module Form exposing , textField, richText, toggle, checkbox, radio, select, file, fileMultiple, datePicker, userPicker, userPickerMultiple, arbitrary, arbitraryWith, unsafeArbitrary , view, viewWithoutSubmit, Model, init, Msg, update, updateValues, getValue, hasFieldsLoading, msgToString , withDisabled + , isDisabled , parse ) @@ -90,6 +91,11 @@ documentation if you're stuck. @docs withDisabled +### Checking attributes and state + +@docs isDisabled + + ## Validating @docs parse @@ -1017,6 +1023,15 @@ withDisabled disabled (Model model) = Model { model | disabled = disabled } +{-| Checks if the form is disabled. It's useful to disable submit buttons when +using `viewWithoutSubmit` or when some parts of the UI should be disabled when +the form is disabled. +-} +isDisabled : Model values -> Bool +isDisabled (Model model) = + model.disabled + + {-| Determines which errors we should show. This is opaque so it can't be modified on the outside -} diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index a35fb78e1..d992e1b1a 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -23,7 +23,7 @@ import Form.Validate import Graphql.Http import Graphql.SelectionSet import Html exposing (Html, button, details, div, h2, img, li, p, span, summary, text, ul) -import Html.Attributes exposing (alt, class, classList, id, src, type_) +import Html.Attributes exposing (alt, class, classList, disabled, id, src, type_) import Html.Attributes.Aria exposing (ariaHasPopup, ariaHidden, ariaLabel) import Html.Events exposing (onClick) import Icons @@ -1480,6 +1480,7 @@ viewCategoryModal translators category formModel = [ button [ class "button button-secondary w-full sm:w-40" , onClick ClosedCategoryModal + , disabled (Form.isDisabled formModel) ] [ text <| translators.t "menu.cancel" ] , button @@ -1491,6 +1492,7 @@ viewCategoryModal translators category formModel = , onSuccess = SubmittedUpdateCategoryForm } ) + , disabled (Form.isDisabled formModel) ] [ text <| translators.t "menu.save" ] ] @@ -1521,6 +1523,7 @@ viewCategoryMetadataModal translators community category formModel = [ button [ class "button button-secondary w-full sm:w-40" , onClick ClosedMetadataModal + , disabled (Form.isDisabled formModel) ] [ text <| translators.t "menu.cancel" ] , button @@ -1532,6 +1535,7 @@ viewCategoryMetadataModal translators community category formModel = , onSuccess = SubmittedMetadataForm } ) + , disabled (Form.isDisabled formModel) ] [ text <| translators.t "menu.save" ] ] From 77764bcb6ccf0be9b195db1dcad1bc3f37c0b91e Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 14:50:35 -0300 Subject: [PATCH 064/104] Remove comment --- src/elm/Page/Community/Settings/Shop/Categories.elm | 1 - 1 file changed, 1 deletion(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index d992e1b1a..3c44dd56b 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1869,7 +1869,6 @@ nameAndSlugForm translators { nameFieldId } = text <| translators.t "shop.categories.form.insert_name" else - -- TODO - I18N text <| translators.t "shop.categories.form.empty_slug" ] ] From 737ac8e0d9b0584748cf3e434f6355f7cb17db85 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 22:50:05 -0300 Subject: [PATCH 065/104] Add icon and image to creation form --- .../Community/Settings/Shop/Categories.elm | 106 +++++++++++------- src/elm/Shop/Category.elm | 8 +- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 3c44dd56b..516e234fc 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -209,8 +209,10 @@ update msg model loggedIn = { parent = maybeParentId , form = Form.init - { name = "" + { icon = Form.File.initSingle { fileUrl = Nothing, aspectRatio = Just 1 } + , name = "" , description = Form.RichText.initModel (newDescriptionInputId maybeParentId) Nothing + , image = Form.File.initSingle { fileUrl = Nothing, aspectRatio = Nothing } } } } @@ -279,7 +281,7 @@ update msg model loggedIn = LoggedIn.addFeedback model - SubmittedAddCategoryForm { name, slug, description } -> + SubmittedAddCategoryForm { icon, name, slug, description, image } -> let parentId = case model.newCategoryState of @@ -303,9 +305,11 @@ update msg model loggedIn = |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.create - { name = name + { icon = icon + , name = name , description = description , slug = slug + , image = image , parentId = parentId } Shop.Category.selectionSet @@ -1638,27 +1642,38 @@ viewShareCategoryPreview translators community category values = type alias NewCategoryFormInput = - { name : String + { icon : Form.File.SingleModel + , name : String , description : Form.RichText.Model + , image : Form.File.SingleModel } type alias NewCategoryFormOutput = - { name : String + { icon : Maybe String + , name : String , slug : Maybe Slug , description : Markdown + , image : Maybe String } newCategoryForm : Translation.Translators -> Form.Form msg NewCategoryFormInput NewCategoryFormOutput newCategoryForm translators = Form.succeed - (\{ name, slug } description -> - { name = name, slug = slug, description = description } + (\icon { name, slug } description image -> + { icon = icon + , name = name + , slug = slug + , description = description + , image = image + } ) + |> Form.with (iconForm translators "new-category-icon") |> Form.with (nameAndSlugForm translators { nameFieldId = "new-category-name" }) |> Form.with (Form.RichText.init { label = translators.t "shop.categories.fields.description" } + |> Form.RichText.withContainerAttrs [ class "mb-10" ] |> Form.richText { parser = Form.Validate.succeed @@ -1669,6 +1684,7 @@ newCategoryForm translators = , externalError = always Nothing } ) + |> Form.with (imageForm translators "new-category-image") type alias UpdateCategoryFormInput = @@ -1689,6 +1705,45 @@ type alias UpdateCategoryFormOutput = } +iconForm : Translation.Translators -> String -> Form.Form msg { input | icon : Form.File.SingleModel } (Maybe String) +iconForm translators fieldId = + Form.File.init { id = fieldId } + |> Form.File.withImageClass "object-cover rounded-full w-20 h-20" + |> Form.File.withEntryContainerAttributes (\_ -> [ class "mx-auto rounded-full w-20 h-20" ]) + |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!rounded-full w-20 h-20" ]) + |> Form.File.withAddImagesContainerAttributes [ class "mx-auto w-20 h-20" ] + |> Form.File.withImageCropperAttributes [ class "rounded-full" ] + |> Form.File.withContainerAttributes [ class "mb-10" ] + |> Form.File.withEditIconOverlay + |> Form.file + { parser = Ok + , translators = translators + , value = .icon + , update = \newIcon values -> { values | icon = newIcon } + , externalError = always Nothing + } + |> Form.optional + + +imageForm : Translation.Translators -> String -> Form.Form msg { input | image : Form.File.SingleModel } (Maybe String) +imageForm translators fieldId = + Form.File.init { id = fieldId } + |> Form.File.withLabel (translators.t "shop.categories.fields.image") + |> Form.File.withContainerAttributes [ class "w-full" ] + |> Form.File.withAddImagesContainerAttributes [ class "!w-full" ] + |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!w-full min-h-48" ]) + |> Form.File.withImageClass "rounded-md" + |> Form.File.withEditIconOverlay + |> Form.file + { parser = Ok + , translators = translators + , value = .image + , update = \newImage values -> { values | image = newImage } + , externalError = always Nothing + } + |> Form.optional + + updateCategoryForm : Translation.Translators -> Shop.Category.Id -> Form.Form msg UpdateCategoryFormInput UpdateCategoryFormOutput updateCategoryForm translators id = Form.succeed @@ -1701,24 +1756,7 @@ updateCategoryForm translators id = , image = image } ) - |> Form.with - (Form.File.init { id = "update-category-icon" } - |> Form.File.withImageClass "object-cover rounded-full w-20 h-20" - |> Form.File.withEntryContainerAttributes (\_ -> [ class "mx-auto rounded-full w-20 h-20" ]) - |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!rounded-full w-20 h-20" ]) - |> Form.File.withAddImagesContainerAttributes [ class "mx-auto w-20 h-20" ] - |> Form.File.withImageCropperAttributes [ class "rounded-full" ] - |> Form.File.withContainerAttributes [ class "mb-10" ] - |> Form.File.withEditIconOverlay - |> Form.file - { parser = Ok - , translators = translators - , value = .icon - , update = \newIcon values -> { values | icon = newIcon } - , externalError = always Nothing - } - |> Form.optional - ) + |> Form.with (iconForm translators "update-category-icon") |> Form.with (nameAndSlugForm translators { nameFieldId = "update-category-name" }) |> Form.with (Form.RichText.init { label = translators.t "shop.categories.fields.description" } @@ -1733,23 +1771,7 @@ updateCategoryForm translators id = , externalError = always Nothing } ) - |> Form.with - (Form.File.init { id = "update-category-image" } - |> Form.File.withLabel (translators.t "shop.categories.fields.image") - |> Form.File.withContainerAttributes [ class "w-full" ] - |> Form.File.withAddImagesContainerAttributes [ class "!w-full" ] - |> Form.File.withAddImagesView (Form.File.defaultAddImagesView [ class "!w-full min-h-48" ]) - |> Form.File.withImageClass "rounded-md" - |> Form.File.withEditIconOverlay - |> Form.file - { parser = Ok - , translators = translators - , value = .image - , update = \newImage values -> { values | image = newImage } - , externalError = always Nothing - } - |> Form.optional - ) + |> Form.with (imageForm translators "update-category-image") type alias MetadataFormInput = diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index c2e8928f6..a082239a3 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -47,14 +47,16 @@ type alias Model = create : - { name : String + { icon : Maybe String + , name : String , description : Markdown , slug : Maybe Slug + , image : Maybe String , parentId : Maybe Id } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -create { name, slug, description, parentId } = +create { icon, name, slug, description, image, parentId } = Cambiatus.Mutation.category (\optionals -> { optionals @@ -62,12 +64,14 @@ create { name, slug, description, parentId } = parentId |> Maybe.map (\(Id id) -> id) |> OptionalArgument.fromMaybe + , iconUri = OptionalArgument.fromMaybe icon , slug = slug |> Maybe.map Slug.toString |> OptionalArgument.fromMaybeWithNull , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) + , imageUri = OptionalArgument.fromMaybe image } ) From 1da472f913e25c821940e2f888746ed944584c5f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 22:53:14 -0300 Subject: [PATCH 066/104] Use icon before image --- src/elm/Page/Community/Settings/Shop/Categories.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 516e234fc..2a5a56616 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1586,7 +1586,7 @@ viewShareCategoryPreview translators community category values = , div [ class "isolate mr-3 z-10 ml-auto w-full sm:w-3/4 md:w-2/3 border border-gray-300 rounded-large relative before:absolute before:bg-white before:border-t before:border-r before:border-gray-300 before:-top-px before:rounded-br-super before:rounded-tr-sm before:-right-2 before:w-8 before:h-4 before:-z-10" ] [ div [ class "bg-white p-1 rounded-large" ] [ div [ class "flex w-full bg-gray-100 rounded-large" ] - [ case Maybe.Extra.or category.image category.icon of + [ case Maybe.Extra.or category.icon category.image of Nothing -> div [ class "bg-gray-200 p-6 rounded-l-large w-1/4 flex-shrink-0 grid place-items-center" ] [ Icons.image "" ] From 7eb8ca1b06b4ebe098b0efd39b373e03e3d698fd Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 23:08:26 -0300 Subject: [PATCH 067/104] Dont accept empty slugs --- public/translations/amh-ETH.json | 2 +- public/translations/cat-CAT.json | 2 +- public/translations/en-US.json | 2 +- public/translations/es-ES.json | 2 +- public/translations/pt-BR.json | 2 +- .../Community/Settings/Shop/Categories.elm | 60 ++++++++++++------- src/elm/Shop/Category.elm | 20 +++---- 7 files changed, 54 insertions(+), 36 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index b76cca840..1a2afc2de 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -893,7 +893,7 @@ }, "form": { "insert_name": "ስሉግን ለማመንጨት ስም ያስገቡ", - "empty_slug": "ምንም ዝቃጭ አይፈጠርም" + "invalid_slug": "ልክ ያልሆነ ሸርተቴ" }, "metadata": { "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 2ae6326ed..16ddf5e69 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -935,7 +935,7 @@ }, "form": { "insert_name": "Insereix un nom per generar el slug", - "empty_slug": "No es generarà cap slug" + "invalid_slug": "Slug no vàlid" }, "metadata": { "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", diff --git a/public/translations/en-US.json b/public/translations/en-US.json index ab75ebd92..3d2cd8efc 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -932,7 +932,7 @@ }, "form": { "insert_name": "Insert a name to generate the slug", - "empty_slug": "No slug will be generated" + "invalid_slug": "Invalid slug" }, "metadata": { "guidance": "This information will be used to display rich links when sharing this category.", diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index cf29cbe5c..e1afc6a8b 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -935,7 +935,7 @@ }, "form": { "insert_name": "Inserta un nombre para generar el slug", - "empty_slug": "No se generará slug" + "invalid_slug": "Slug no válido" }, "metadata": { "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index d188e9f3a..09cb2e664 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -939,7 +939,7 @@ }, "form": { "insert_name": "Insira um nome para gerar o slug", - "empty_slug": "Nenhum slug será gerado" + "invalid_slug": "Slug inválido" }, "metadata": { "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 2a5a56616..9a712212d 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1652,7 +1652,7 @@ type alias NewCategoryFormInput = type alias NewCategoryFormOutput = { icon : Maybe String , name : String - , slug : Maybe Slug + , slug : Slug , description : Markdown , image : Maybe String } @@ -1699,7 +1699,7 @@ type alias UpdateCategoryFormOutput = { id : Shop.Category.Id , icon : Maybe String , name : String - , slug : Maybe Slug + , slug : Slug , description : Markdown , image : Maybe String } @@ -1854,7 +1854,7 @@ metadataForm translators community category = ) -nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Maybe Slug } +nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Slug } nameAndSlugForm translators { nameFieldId } = Form.succeed (\name slug -> { name = name, slug = slug }) |> Form.with @@ -1867,6 +1867,16 @@ nameAndSlugForm translators { nameFieldId } = { parser = Form.Validate.succeed >> Form.Validate.stringLongerThan 2 + >> Form.Validate.custom + (\name -> + case Slug.generate name of + Just _ -> + Ok name + + Nothing -> + -- We show the error on the slug field below + Err (\_ -> "") + ) >> Form.Validate.validate translators , value = .name , update = \newName values -> { values | name = newName } @@ -1875,26 +1885,34 @@ nameAndSlugForm translators { nameFieldId } = ) |> Form.with ((\{ name } -> - Form.arbitraryWith (Slug.generate name) - (div [ class "mb-10" ] - [ View.Components.label [] - { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } - , case Slug.generate name of - Just slug -> - -- TODO - We should show a preview of the url, like: - -- TODO - "Your url will look like muda.cambiatus.io/shop/categories/organicos--1234" - text (Slug.toString slug) + case Slug.generate name of + Nothing -> + Form.arbitrary + (div [ class "mb-10" ] + [ View.Components.label [] + { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } + , if String.isEmpty name then + span [ class "text-gray-400 italic" ] + [ text <| translators.t "shop.categories.form.insert_name" ] + + else + span [ class "form-error" ] + -- TODO - Add tooltip? + [ text <| translators.t "shop.categories.form.invalid_slug" ] + ] + ) - Nothing -> - span [ class "text-gray-400 italic" ] - [ if String.isEmpty name then - text <| translators.t "shop.categories.form.insert_name" + Just slug -> + Form.arbitraryWith slug + (div [ class "mb-10" ] + [ View.Components.label [] + { targetId = nameFieldId, labelText = translators.t "shop.categories.fields.slug" } - else - text <| translators.t "shop.categories.form.empty_slug" - ] - ] - ) + -- TODO - We should show a preview of the url, like: + -- TODO - "Your url will look like muda.cambiatus.io/shop/categories/organicos--1234" + , text (Slug.toString slug) + ] + ) ) |> Form.introspect ) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index a082239a3..8a2bf9859 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -50,7 +50,7 @@ create : { icon : Maybe String , name : String , description : Markdown - , slug : Maybe Slug + , slug : Slug , image : Maybe String , parentId : Maybe Id } @@ -65,10 +65,7 @@ create { icon, name, slug, description, image, parentId } = |> Maybe.map (\(Id id) -> id) |> OptionalArgument.fromMaybe , iconUri = OptionalArgument.fromMaybe icon - , slug = - slug - |> Maybe.map Slug.toString - |> OptionalArgument.fromMaybeWithNull + , slug = OptionalArgument.Present (Slug.toString slug) , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) , imageUri = OptionalArgument.fromMaybe image @@ -78,7 +75,13 @@ create { icon, name, slug, description, image, parentId } = update : Model - -> { icon : Maybe String, name : String, description : Markdown, slug : Maybe Slug, image : Maybe String } + -> + { icon : Maybe String + , name : String + , description : Markdown + , slug : Slug + , image : Maybe String + } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation update model { icon, name, description, slug, image } = @@ -88,10 +91,7 @@ update model { icon, name, description, slug, image } = | id = OptionalArgument.Present (unwrapId model.id) , iconUri = OptionalArgument.fromMaybeWithNull icon , imageUri = OptionalArgument.fromMaybeWithNull image - , slug = - slug - |> Maybe.map Slug.toString - |> OptionalArgument.fromMaybeWithNull + , slug = OptionalArgument.Present (Slug.toString slug) , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) } From e89a15cb9ac62392c833270193d6a6d220b6f41e Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 23:14:11 -0300 Subject: [PATCH 068/104] Add tooltip when slug is invalid --- src/elm/Form/Toggle.elm | 4 ++-- src/elm/Page/Community/New.elm | 1 + src/elm/Page/Community/Settings/ActionEditor.elm | 1 + src/elm/Page/Community/Settings/Features.elm | 1 + src/elm/Page/Community/Settings/Shop/Categories.elm | 13 ++++++++++--- src/elm/View/Components.elm | 6 +++--- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/elm/Form/Toggle.elm b/src/elm/Form/Toggle.elm index 32f8b2e83..4a28e828c 100644 --- a/src/elm/Form/Toggle.elm +++ b/src/elm/Form/Toggle.elm @@ -66,7 +66,7 @@ type Options msg { label : Html msg , id : String , disabled : Bool - , tooltip : Maybe { message : String, iconClass : String } + , tooltip : Maybe { message : String, iconClass : String, containerClass : String } , side : Side , statusText : StatusText , topLabel : Maybe String @@ -109,7 +109,7 @@ type StatusText {-| Adds a tooltip the user can see when hovering over an icon. Useful when we need to give more information to the user -} -withTooltip : { message : String, iconClass : String } -> Options msg -> Options msg +withTooltip : { message : String, iconClass : String, containerClass : String } -> Options msg -> Options msg withTooltip tooltip (Options options) = Options { options | tooltip = Just tooltip } diff --git a/src/elm/Page/Community/New.elm b/src/elm/Page/Community/New.elm index 7df2d9471..d6e2df0e2 100755 --- a/src/elm/Page/Community/New.elm +++ b/src/elm/Page/Community/New.elm @@ -323,6 +323,7 @@ requireInvitationToggle { t } = |> Form.Toggle.withTooltip { message = t "settings.community_info.invitation.description" , iconClass = "text-orange-300" + , containerClass = "" } |> Form.Toggle.withContainerAttrs [] |> Form.toggle diff --git a/src/elm/Page/Community/Settings/ActionEditor.elm b/src/elm/Page/Community/Settings/ActionEditor.elm index e1c62aaf4..5a9438b42 100755 --- a/src/elm/Page/Community/Settings/ActionEditor.elm +++ b/src/elm/Page/Community/Settings/ActionEditor.elm @@ -756,6 +756,7 @@ verificationForm loggedIn community = , View.Components.tooltip { message = t "community.actions.form.automatic_tooltip" , iconClass = "" + , containerClass = "" } ] ) diff --git a/src/elm/Page/Community/Settings/Features.elm b/src/elm/Page/Community/Settings/Features.elm index 723edeb9e..8792bc74e 100644 --- a/src/elm/Page/Community/Settings/Features.elm +++ b/src/elm/Page/Community/Settings/Features.elm @@ -153,6 +153,7 @@ view loggedIn model = Just { message = t "community.kyc.info" , iconClass = "text-orange-300" + , containerClass = "" } } , { label = "sponsorship.title" diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 9a712212d..0394f51ce 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1896,9 +1896,16 @@ nameAndSlugForm translators { nameFieldId } = [ text <| translators.t "shop.categories.form.insert_name" ] else - span [ class "form-error" ] - -- TODO - Add tooltip? - [ text <| translators.t "shop.categories.form.invalid_slug" ] + div [ class "flex" ] + [ span [ class "form-error" ] + [ text <| translators.t "shop.categories.form.invalid_slug" ] + , View.Components.tooltip + -- TODO - I18N + { message = "Non-latin characters aren't accepted in slugs. In order to generate a valid slug, the category name must have at least one latin character" + , iconClass = "text-red" + , containerClass = "self-end" + } + ] ] ) diff --git a/src/elm/View/Components.elm b/src/elm/View/Components.elm index 67fb0ae36..7462f98e4 100644 --- a/src/elm/View/Components.elm +++ b/src/elm/View/Components.elm @@ -142,9 +142,9 @@ masonryLayout breakpoints { transitionWithParent } attrs children = -- ELEMENTS -tooltip : { message : String, iconClass : String } -> Html msg -tooltip { message, iconClass } = - span [ class "icon-tooltip ml-1 z-10" ] +tooltip : { message : String, iconClass : String, containerClass : String } -> Html msg +tooltip { message, iconClass, containerClass } = + span [ class ("icon-tooltip ml-1 z-10 " ++ containerClass) ] [ Icons.question ("inline-block " ++ iconClass) , p [ class "icon-tooltip-content" ] [ text message ] From 63e703a52a9d7de28e1b584ae6805da5f422b69d Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 23:17:23 -0300 Subject: [PATCH 069/104] Add translations for invalid slug tooltip --- public/translations/amh-ETH.json | 3 ++- public/translations/cat-CAT.json | 3 ++- public/translations/en-US.json | 3 ++- public/translations/es-ES.json | 3 ++- public/translations/pt-BR.json | 3 ++- src/elm/Page/Community/Settings/Shop/Categories.elm | 3 +-- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index 1a2afc2de..ebbec6f78 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -893,7 +893,8 @@ }, "form": { "insert_name": "ስሉግን ለማመንጨት ስም ያስገቡ", - "invalid_slug": "ልክ ያልሆነ ሸርተቴ" + "invalid_slug": "ልክ ያልሆነ ሸርተቴ", + "invalid_slug_tooltip": "የላቲን ያልሆኑ ገጸ-ባህሪያት በስሎግስ ውስጥ ተቀባይነት የላቸውም። የሚሰራ ስሉግ ለማመንጨት የምድብ ስም ቢያንስ አንድ የላቲን ቁምፊ ሊኖረው ይገባል" }, "metadata": { "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 16ddf5e69..a2efd32dd 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -935,7 +935,8 @@ }, "form": { "insert_name": "Insereix un nom per generar el slug", - "invalid_slug": "Slug no vàlid" + "invalid_slug": "Slug no vàlid", + "invalid_slug_tooltip": "Els caràcters no llatins no s'accepten als slugs. Per generar un slug vàlid, el nom de la categoria ha de tenir almenys un caràcter llatí" }, "metadata": { "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", diff --git a/public/translations/en-US.json b/public/translations/en-US.json index 3d2cd8efc..0cdfe720e 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -932,7 +932,8 @@ }, "form": { "insert_name": "Insert a name to generate the slug", - "invalid_slug": "Invalid slug" + "invalid_slug": "Invalid slug", + "invalid_slug_tooltip": "Non-latin characters aren't accepted in slugs. In order to generate a valid slug, the category name must have at least one latin character" }, "metadata": { "guidance": "This information will be used to display rich links when sharing this category.", diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index e1afc6a8b..3a0319e95 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -935,7 +935,8 @@ }, "form": { "insert_name": "Inserta un nombre para generar el slug", - "invalid_slug": "Slug no válido" + "invalid_slug": "Slug no válido", + "invalid_slug_tooltip": "Los caracteres no latinos no se aceptan en slugs. Para generar un slug válido, el nombre de la categoría debe tener al menos un carácter latino" }, "metadata": { "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index 09cb2e664..1356b3cfd 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -939,7 +939,8 @@ }, "form": { "insert_name": "Insira um nome para gerar o slug", - "invalid_slug": "Slug inválido" + "invalid_slug": "Slug inválido", + "invalid_slug_tooltip": "Carácteres não-latinos não são aceitos em slugs. Para gerar um slug válido, o nome da categoria deve ter ao menos um caráctere latim" }, "metadata": { "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 0394f51ce..32d76cbba 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1900,8 +1900,7 @@ nameAndSlugForm translators { nameFieldId } = [ span [ class "form-error" ] [ text <| translators.t "shop.categories.form.invalid_slug" ] , View.Components.tooltip - -- TODO - I18N - { message = "Non-latin characters aren't accepted in slugs. In order to generate a valid slug, the category name must have at least one latin character" + { message = translators.t "shop.categories.form.invalid_slug_tooltip" , iconClass = "text-red" , containerClass = "self-end" } From a515c7755cd19faf7ef112c8d0d21f2f3b006732 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 23:36:46 -0300 Subject: [PATCH 070/104] Make keywords a list of strings --- .../Community/Settings/Shop/Categories.elm | 87 ++++++++++++++----- src/elm/Shop/Category.elm | 14 ++- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 32d76cbba..1ba04ab8f 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -594,7 +594,8 @@ update msg model loggedIn = (Form.init { metaTitle = Maybe.withDefault category.name category.metaTitle , metaDescription = Maybe.withDefault (Markdown.toUnformattedString category.description) category.metaDescription - , metaKeywords = Maybe.withDefault "" category.metaKeywords + , metaKeyword = "" + , metaKeywords = category.metaKeywords } ) } @@ -1777,7 +1778,8 @@ updateCategoryForm translators id = type alias MetadataFormInput = { metaTitle : String , metaDescription : String - , metaKeywords : String + , metaKeyword : String + , metaKeywords : List String } @@ -1785,9 +1787,7 @@ type alias MetadataFormOutput = { id : Shop.Category.Id , metaTitle : String , metaDescription : String - - -- TODO - Should this be a List String? - , metaKeywords : String + , metaKeywords : List String } @@ -1833,27 +1833,74 @@ metadataForm translators community category = , externalError = always Nothing } ) - |> Form.with - (Form.Text.init - { label = translators.t "shop.categories.fields.meta.keywords" - , id = "meta-keywords-input" - } - |> Form.textField - { parser = - Form.Validate.succeed - -- TODO - Review this validation - >> Form.Validate.validate translators - , value = .metaKeywords - , update = \newMetaKeywords values -> { values | metaKeywords = newMetaKeywords } - , externalError = always Nothing - } - ) + |> Form.with (keywordsForm translators) |> Form.withNoOutput (Form.introspect (\values -> Form.arbitrary (viewShareCategoryPreview translators community category values)) ) +keywordsForm : Translation.Translators -> Form.Form msg { input | metaKeyword : String, metaKeywords : List String } (List String) +keywordsForm translators = + let + viewKeyword keyword = + div [ class "bg-green/50 border border-green px-3 py-2 rounded-full text-sm flex items-center text-black" ] + [ span [ class "uppercase mr-3" ] [ text keyword ] + , button + [ type_ "button" + , onClick (\values -> { values | metaKeywords = List.filter (\x -> x /= keyword) values.metaKeywords }) + , class "hover:text-red" + ] + [ Icons.close "w-3 h-3 fill-current" ] + ] + in + Form.introspect (\values -> Form.succeed values.metaKeywords) + |> Form.withNoOutput + (Form.succeed always + |> Form.withGroup [ class "flex mb-2" ] + (Form.Text.init + { label = translators.t "shop.categories.fields.meta.keywords" + , id = "meta-keyword-input" + } + |> Form.Text.withContainerAttrs [ class "mb-0 w-full" ] + |> Form.textField + { parser = Ok + , value = .metaKeyword + , update = \metaKeyword value -> { value | metaKeyword = metaKeyword } + , externalError = always Nothing + } + ) + (Form.arbitrary + (button + [ type_ "button" + , class "button button-secondary flex-shrink-0 h-12 ml-4 mt-auto w-auto px-6" + , onClick + (\values -> + if String.isEmpty values.metaKeyword || List.member values.metaKeyword values.metaKeywords then + values + + else + { values + | metaKeyword = "" + , metaKeywords = values.metaKeyword :: values.metaKeywords + } + ) + ] + [ text <| translators.t "menu.add" ] + ) + ) + ) + |> Form.withNoOutput + (Form.introspect + (\values -> + Form.arbitrary + (div [ class "flex flex-wrap mb-10 gap-2" ] + (List.map viewKeyword values.metaKeywords) + ) + ) + ) + + nameAndSlugForm : Translation.Translators -> { nameFieldId : String } -> Form.Form msg { values | name : String } { name : String, slug : Slug } nameAndSlugForm translators { nameFieldId } = Form.succeed (\name slug -> { name = name, slug = slug }) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 8a2bf9859..a8f522373 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -42,7 +42,7 @@ type alias Model = , parentId : Maybe Id , metaTitle : Maybe String , metaDescription : Maybe String - , metaKeywords : Maybe String + , metaKeywords : List String } @@ -100,7 +100,7 @@ update model { icon, name, description, slug, image } = updateMetadata : Model - -> { metaTitle : String, metaDescription : String, metaKeywords : String } + -> { metaTitle : String, metaDescription : String, metaKeywords : List String } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation updateMetadata model { metaTitle, metaDescription, metaKeywords } = @@ -109,7 +109,7 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = { optionals | id = OptionalArgument.Present (unwrapId model.id) , metaDescription = OptionalArgument.Present metaDescription - , metaKeywords = OptionalArgument.Present metaKeywords + , metaKeywords = OptionalArgument.Present (String.join ", " metaKeywords) , metaTitle = OptionalArgument.Present metaTitle } ) @@ -170,7 +170,13 @@ selectionSet = |> SelectionSet.with (Cambiatus.Object.Category.parent idSelectionSet) |> SelectionSet.with Cambiatus.Object.Category.metaTitle |> SelectionSet.with Cambiatus.Object.Category.metaDescription - |> SelectionSet.with Cambiatus.Object.Category.metaKeywords + |> SelectionSet.with + (Cambiatus.Object.Category.metaKeywords + |> SelectionSet.map + (Maybe.map (String.split "," >> List.map String.trim) + >> Maybe.withDefault [] + ) + ) From c15b4c5f8fa6204594611856eb6318fb2c6cce37 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 23 Jun 2022 23:38:20 -0300 Subject: [PATCH 071/104] Show keywords in link preview --- src/elm/Page/Community/Settings/Shop/Categories.elm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 1ba04ab8f..984d37f03 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1625,7 +1625,10 @@ viewShareCategoryPreview translators community category values = else p [ class "text-sm mt-1 line-clamp-2" ] - [ text values.metaDescription ] + [ text values.metaDescription + , text ". " + , text (String.join ", " values.metaKeywords) + ] , p [ class "text-sm opacity-70 mt-2 mb-4" ] [ text community.subdomain ] ] From 9df5202a1c944ba7ed932ff5efd1baeb370fbfe1 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 24 Jun 2022 00:09:48 -0300 Subject: [PATCH 072/104] Adjust draggingElement and draggingOverElement in some cases --- src/elm/Dnd.elm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/elm/Dnd.elm b/src/elm/Dnd.elm index c5f989add..21a9dcac1 100644 --- a/src/elm/Dnd.elm +++ b/src/elm/Dnd.elm @@ -48,7 +48,10 @@ update msg (Model model) = |> UR.init StoppedDragging -> - { model | draggingElement = Nothing } + { model + | draggingElement = Nothing + , draggingOverElement = Nothing + } |> Model |> UR.init @@ -60,7 +63,10 @@ update msg (Model model) = |> UR.init Just draggingElement -> - model + { model + | draggingElement = Nothing + , draggingOverElement = Nothing + } |> Model |> UR.init |> UR.addExt (Dropped { draggedElement = draggingElement, dropZone = elementId }) From cda36098c7e19ec5bb37a95b797324d5b88db3f4 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 24 Jun 2022 00:10:27 -0300 Subject: [PATCH 073/104] Allow reordering keywords --- .../Community/Settings/Shop/Categories.elm | 118 ++++++++++++++++-- 1 file changed, 111 insertions(+), 7 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 984d37f03..5e555331f 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -132,6 +132,8 @@ type Msg | CompletedMovingCategoryToRoot Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) | ClickedMoveUp Shop.Category.Id | ClickedMoveDown Shop.Category.Id + | GotKeywordDndMsg (Dnd.Msg Int Int) + | ClickedRemoveMetaKeyword Int type alias UpdateResult = @@ -596,6 +598,7 @@ update msg model loggedIn = , metaDescription = Maybe.withDefault (Markdown.toUnformattedString category.description) category.metaDescription , metaKeyword = "" , metaKeywords = category.metaKeywords + , keywordsDnd = Dnd.init } ) } @@ -882,6 +885,75 @@ update msg model loggedIn = _ -> UR.init model + GotKeywordDndMsg subMsg -> + case model.categoryMetadataModalState of + Closed -> + UR.init model + + Open categoryId formModel -> + Dnd.update subMsg (Form.getValue .keywordsDnd formModel) + |> UR.fromChild + (\newDnd -> + { model + | categoryMetadataModalState = + Open categoryId + (Form.updateValues + (\values -> { values | keywordsDnd = newDnd }) + formModel + ) + } + ) + GotKeywordDndMsg + updateKeywordsDnd + model + + ClickedRemoveMetaKeyword index -> + case model.categoryMetadataModalState of + Closed -> + UR.init model + + Open categoryId formModel -> + { model + | categoryMetadataModalState = + formModel + |> Form.updateValues (\values -> { values | metaKeywords = List.Extra.removeAt index values.metaKeywords }) + |> Open categoryId + } + |> UR.init + + +updateKeywordsDnd : Dnd.ExtMsg Int Int -> UpdateResult -> UpdateResult +updateKeywordsDnd ext ur = + case ext of + Dnd.Dropped { draggedElement, dropZone } -> + case ur.model.categoryMetadataModalState of + Closed -> + ur + + Open categoryId formModel -> + UR.mapModel + (\model -> + { model + | categoryMetadataModalState = + Open categoryId + (Form.updateValues + (\values -> + { values + | metaKeywords = + List.Extra.swapAt draggedElement + dropZone + values.metaKeywords + } + ) + formModel + ) + } + ) + ur + + Dnd.DraggedOver _ -> + ur + updateDnd : LoggedIn.Model -> Dnd.ExtMsg Shop.Category.Id DropZone -> UpdateResult -> UpdateResult updateDnd loggedIn ext ur = @@ -1783,6 +1855,7 @@ type alias MetadataFormInput = , metaDescription : String , metaKeyword : String , metaKeywords : List String + , keywordsDnd : Dnd.Model Int Int } @@ -1794,7 +1867,7 @@ type alias MetadataFormOutput = } -metadataForm : Translation.Translators -> Community.Model -> Shop.Category.Model -> Form.Form msg MetadataFormInput MetadataFormOutput +metadataForm : Translation.Translators -> Community.Model -> Shop.Category.Model -> Form.Form Msg MetadataFormInput MetadataFormOutput metadataForm translators community category = Form.succeed (\metaTitle metaDescription metaKeywords -> @@ -1843,15 +1916,38 @@ metadataForm translators community category = ) -keywordsForm : Translation.Translators -> Form.Form msg { input | metaKeyword : String, metaKeywords : List String } (List String) +keywordsForm : + Translation.Translators + -> + Form.Form + Msg + { input + | metaKeyword : String + , metaKeywords : List String + , keywordsDnd : Dnd.Model Int Int + } + (List String) keywordsForm translators = let - viewKeyword keyword = - div [ class "bg-green/50 border border-green px-3 py-2 rounded-full text-sm flex items-center text-black" ] + viewKeyword dnd index keyword = + div + (class "bg-green/50 border border-green px-3 py-2 rounded-full text-sm flex items-center text-black cursor-move transition-colors" + :: classList + [ ( "border-dashed border-black !bg-green/80", Dnd.getDraggingOverElement dnd == Just index ) + , ( "!bg-green/80", Dnd.getDraggingElement dnd == Just index ) + ] + :: Dnd.draggable index GotKeywordDndMsg + ++ (if Dnd.getDraggingElement dnd == Just index then + [] + + else + Dnd.dropZone index GotKeywordDndMsg + ) + ) [ span [ class "uppercase mr-3" ] [ text keyword ] , button [ type_ "button" - , onClick (\values -> { values | metaKeywords = List.filter (\x -> x /= keyword) values.metaKeywords }) + , onClick (ClickedRemoveMetaKeyword index) , class "hover:text-red" ] [ Icons.close "w-3 h-3 fill-current" ] @@ -1896,9 +1992,11 @@ keywordsForm translators = |> Form.withNoOutput (Form.introspect (\values -> - Form.arbitrary + Form.unsafeArbitrary (div [ class "flex flex-wrap mb-10 gap-2" ] - (List.map viewKeyword values.metaKeywords) + (List.indexedMap (viewKeyword values.keywordsDnd) + values.metaKeywords + ) ) ) ) @@ -2184,3 +2282,9 @@ msgToString msg = ClickedMoveDown _ -> [ "ClickedMoveDown" ] + + GotKeywordDndMsg subMsg -> + "GotKeywordDndMsg" :: Dnd.msgToString subMsg + + ClickedRemoveMetaKeyword _ -> + [ "ClickedRemoveMetaKeyword" ] From 3551497f85d332a1c7d31b5cc7304f478f374f85 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 24 Jun 2022 00:11:38 -0300 Subject: [PATCH 074/104] Fix elm-book build --- elm-book/src/elm/Book/Form/Toggle.elm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elm-book/src/elm/Book/Form/Toggle.elm b/elm-book/src/elm/Book/Form/Toggle.elm index fdb629218..171158ab4 100644 --- a/elm-book/src/elm/Book/Form/Toggle.elm +++ b/elm-book/src/elm/Book/Form/Toggle.elm @@ -81,7 +81,7 @@ view model = identity else - Form.Toggle.withTooltip { message = model.tooltip, iconClass = "" } + Form.Toggle.withTooltip { message = model.tooltip, iconClass = "", containerClass = "" } in Html.div [ Html.Attributes.class "space-y-4" ] [ Html.div [] @@ -170,6 +170,7 @@ chapter = |> Form.Toggle.withTooltip { message = "Some longer description about what the toggle does. By the way, we can also add attributes to the question mark icon, such as changing the color on it" , iconClass = "" + , containerClass = "" } ) (baseViewOptions False) From 12839e881a945e29847cff93f4ab1ad58d247b94 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 24 Jun 2022 14:20:43 -0300 Subject: [PATCH 075/104] Fix elm.json --- elm.json | 1 - 1 file changed, 1 deletion(-) diff --git a/elm.json b/elm.json index 002c45914..9303495be 100755 --- a/elm.json +++ b/elm.json @@ -56,7 +56,6 @@ "elm-community/json-extra": "4.3.0", "lukewestby/elm-string-interpolate": "1.0.4", "myrho/elm-round": "1.0.4", - "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", "stil4m/elm-syntax": "7.1.3", "stil4m/structured-writer": "1.0.3" From 53ffb72495b71ed37c1a6c255f424a7c3ffa25ec Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Fri, 24 Jun 2022 14:24:07 -0300 Subject: [PATCH 076/104] Show category icon if present --- src/elm/Page/Community/Settings/Shop/Categories.elm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 5e555331f..64334f2a4 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1329,6 +1329,12 @@ viewCategoryWithChildren translators model zipper children = ) [ div [ class "flex items-center sticky left-0 w-full" ] [ Icons.arrowDown (String.join " " [ "transition-transform", openArrowClass ]) + , case category.icon of + Nothing -> + text "" + + Just icon -> + img [ src icon, alt "", class "h-6 w-6 rounded-full mr-2" ] [] , button [ class "hover:underline focus-ring whitespace-nowrap" , Utils.onClickNoBubble (ClickedCategory category.id) From c857f3ee2e6e0a25ebbaa8da4bfc6bcbe5163dfa Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 20:06:03 -0300 Subject: [PATCH 077/104] Move edit category action to menu --- .../Community/Settings/Shop/Categories.elm | 79 +++++++++++++------ tailwind.config.js | 2 +- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 64334f2a4..f345a64de 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1318,10 +1318,10 @@ viewCategoryWithChildren translators model zipper children = , classList [ ( "pointer-events-none", EverySet.member category.id model.deleting ) ] ] [ summary - (class "marker-hidden flex items-center rounded-sm transition-colors cursor-pointer" + (class "marker-hidden flex items-center rounded-sm transition-colors cursor-pointer grand-parent focus-ring" :: classList [ ( "!bg-green/20", isParentOfNewCategoryForm ) - , ( "focus:bg-orange-100/20 focus-ring parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) + , ( "focus:bg-orange-100/20 parent-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) , ( "bg-orange-100/20", hasActionsMenuOpen ) ] :: onClick (ClickedToggleExpandCategory category.id) @@ -1335,11 +1335,7 @@ viewCategoryWithChildren translators model zipper children = Just icon -> img [ src icon, alt "", class "h-6 w-6 rounded-full mr-2" ] [] - , button - [ class "hover:underline focus-ring whitespace-nowrap" - , Utils.onClickNoBubble (ClickedCategory category.id) - , ariaLabel <| translators.tr "shop.categories.click_category_to_edit" [ ( "category_name", category.name ) ] - ] + , span [ class "whitespace-nowrap" ] [ text category.name ] ] @@ -1351,12 +1347,9 @@ viewCategoryWithChildren translators model zipper children = ] ] [ viewActions translators - [ classList - [ ( "!bg-green/20", isParentOfNewCategoryForm ) - , ( "grand-parent-3-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) - , ( "bg-orange-100/20", hasActionsMenuOpen ) - ] - ] + { isParentOfNewCategoryForm = isParentOfNewCategoryForm + , isDraggingSomething = isDraggingSomething + } model zipper ] @@ -1435,8 +1428,16 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs -viewActions : Translation.Translators -> List (Html.Attribute Msg) -> Model -> Tree.Zipper.Zipper Shop.Category.Model -> Html Msg -viewActions translators attrs model zipper = +viewActions : + Translation.Translators + -> + { isParentOfNewCategoryForm : Bool + , isDraggingSomething : Bool + } + -> Model + -> Tree.Zipper.Zipper Shop.Category.Model + -> Html Msg +viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model zipper = let category = Tree.Zipper.label zipper @@ -1460,26 +1461,54 @@ viewActions translators attrs model zipper = (Maybe.Extra.isNothing (goUpWithoutChildren zipper) && Maybe.Extra.isNothing (Tree.Zipper.parent zipper) ) + + buttonClassListsFromParent = + classList + [ ( "hover:!bg-orange-300/30 active:!bg-orange-300/60 focus:bg-orange-300/30", not isParentOfNewCategoryForm ) + , ( "hover:!bg-green/30 active:!bg-green/60 focus:!bg-green/30", isParentOfNewCategoryForm ) + ] in - div [ class "relative" ] + div + [ class "relative flex rounded-sm transition-colors" + , classList + [ ( "bg-green/20", isParentOfNewCategoryForm ) + , ( "grand-parent-1-focus:bg-orange-100/20 grand-parent-2-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) + , ( "bg-orange-100/20", isDropdownOpen ) + ] + ] [ button - (class "h-8 px-2 rounded-sm transition-colors hover:!bg-orange-300/30 active:!bg-orange-300/60 action-opener focus-ring focus:bg-orange-300/30" - :: classList [ ( "bg-orange-300/60", isDropdownOpen ) ] - :: Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) - :: ariaHasPopup "true" - :: ariaLabel (translators.tr "shop.categories.action_for" [ ( "category_name", category.name ) ]) - :: attrs - ) + [ class "hidden sm:block h-8 px-2 mr-2 rounded-sm transition-colors hover:!bg-orange-300/30 active:!bg-orange-300/60 focus-ring focus:bg-orange-300/30" + , Utils.onClickNoBubble (ClickedCategory category.id) + , buttonClassListsFromParent + , ariaLabel (translators.tr "shop.categories.click_category_to_edit" [ ( "category_name", category.name ) ]) + ] + [ Icons.edit "max-h-4 w-8" + ] + , div + [ class "w-2" + ] + [] + , button + [ class "h-8 px-2 rounded-sm transition-colors action-opener focus-ring" + , classList + [ ( "bg-orange-300/60", isDropdownOpen && not isParentOfNewCategoryForm ) + , ( "bg-green/30", isDropdownOpen && isParentOfNewCategoryForm ) + ] + , buttonClassListsFromParent + , Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) + , ariaHasPopup "true" + , ariaLabel (translators.tr "shop.categories.action_for" [ ( "category_name", category.name ) ]) + ] [ Icons.ellipsis "h-4 pointer-events-none text-gray-800" ] , if not isDropdownOpen then text "" else ul - [ class "absolute right-0 bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" + [ class "absolute right-0 top-full bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" ] [ li [] - [ viewAction [] + [ viewAction [ class "sm:hidden" ] { icon = Icons.edit "w-4 ml-1 mr-3" , label = translators.t "shop.categories.actions.edit_main_information" , onClickMsg = ClickedCategory category.id diff --git a/tailwind.config.js b/tailwind.config.js index f7a3784b1..54ed1cea0 100755 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -283,7 +283,7 @@ module.exports = { // Create `grand-parent-*` variants. This works similar to `group-*` variants, but // only works on direct children of the direct children of the `grand-parent` class. function ({ addVariant, e }) { - const operations = ['hover'] + const operations = ['hover', 'focus'] operations.forEach((operation) => { [1, 2, 3, 4, 5].forEach((level) => { From 3d8559e59085f2d16283bf42a1bba002be15d70f Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 20:06:35 -0300 Subject: [PATCH 078/104] Renamed ClickedCategory -> ClickedEditCategory --- src/elm/Page/Community/Settings/Shop/Categories.elm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index f345a64de..4fab0bebe 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -108,7 +108,7 @@ type Msg | ClickedToggleExpandCategory Shop.Category.Id | ClickedAddCategory (Maybe Shop.Category.Id) | ClickedCancelAddCategory - | ClickedCategory Shop.Category.Id + | ClickedEditCategory Shop.Category.Id | ClosedCategoryModal | GotAddCategoryFormMsg (Form.Msg NewCategoryFormInput) | SubmittedAddCategoryForm NewCategoryFormOutput @@ -228,7 +228,7 @@ update msg model loggedIn = { model | newCategoryState = NotEditing } |> UR.init - ClickedCategory categoryId -> + ClickedEditCategory categoryId -> case getCategoryZipper categoryId |> Maybe.map Tree.Zipper.label of Just category -> { model @@ -1478,7 +1478,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model ] [ button [ class "hidden sm:block h-8 px-2 mr-2 rounded-sm transition-colors hover:!bg-orange-300/30 active:!bg-orange-300/60 focus-ring focus:bg-orange-300/30" - , Utils.onClickNoBubble (ClickedCategory category.id) + , Utils.onClickNoBubble (ClickedEditCategory category.id) , buttonClassListsFromParent , ariaLabel (translators.tr "shop.categories.click_category_to_edit" [ ( "category_name", category.name ) ]) ] @@ -1511,7 +1511,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model [ viewAction [ class "sm:hidden" ] { icon = Icons.edit "w-4 ml-1 mr-3" , label = translators.t "shop.categories.actions.edit_main_information" - , onClickMsg = ClickedCategory category.id + , onClickMsg = ClickedEditCategory category.id } ] , li [] @@ -2246,8 +2246,8 @@ msgToString msg = ClickedCancelAddCategory -> [ "ClickedCancelAddCategory" ] - ClickedCategory _ -> - [ "ClickedCategory" ] + ClickedEditCategory _ -> + [ "ClickedEditCategory" ] ClosedCategoryModal -> [ "ClosedCategoryModal" ] From f1abca1046462777e4f3d291b13bededb5b78188 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 20:53:18 -0300 Subject: [PATCH 079/104] Change root category dropzone and button, remove useless element --- .../Community/Settings/Shop/Categories.elm | 120 +++++++++++------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 4fab0bebe..8fcf0937e 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1100,6 +1100,16 @@ view_ translators community model categories = isDraggingSomething = Maybe.Extra.isJust (Dnd.getDraggingElement model.dnd) + isDraggingRootCategory = + case Dnd.getDraggingElement model.dnd of + Nothing -> + False + + Just draggingId -> + categories + |> List.map Tree.label + |> List.any (\tree -> tree.id == draggingId) + isDraggingOverAddCategory = case Dnd.getDraggingOverElement model.dnd of Nothing -> @@ -1110,6 +1120,18 @@ view_ translators community model categories = Just OnRoot -> True + + maybeNewRootCategoryForm = + case model.newCategoryState of + NotEditing -> + Nothing + + EditingNewCategory { parent, form } -> + if Maybe.Extra.isNothing parent then + Just form + + else + Nothing in viewPageContainer { children = @@ -1142,16 +1164,31 @@ view_ translators community model categories = ) categories ) - , viewAddCategory translators - (class "w-full sticky left-0" + , div + (class "w-full rounded-sm transition-all ease-out" :: classList - [ ( "bg-green/30", isDraggingSomething ) - , ( "outline-black outline-offset-0", isDraggingSomething && isDraggingOverAddCategory ) + [ ( "h-0", not isDraggingSomething ) + , ( "bg-green/30 h-8", isDraggingSomething && not isDraggingRootCategory ) + , ( "outline-black outline-offset-0", isDraggingSomething && isDraggingOverAddCategory && not isDraggingRootCategory ) ] - :: Dnd.dropZone OnRoot GotDndMsg + :: (if isDraggingRootCategory then + [] + + else + Dnd.dropZone OnRoot GotDndMsg + ) ) - model - Nothing + [] + , case maybeNewRootCategoryForm of + Nothing -> + button + [ class "button button-primary w-full sticky left-0 mt-8" + , onClick (ClickedAddCategory Nothing) + ] + [ text <| translators.t "shop.categories.add_root" ] + + Just formModel -> + viewNewCategoryForm [] translators formModel ] , modals = [ case model.categoryModalState of @@ -1362,7 +1399,7 @@ viewCategoryWithChildren translators model zipper children = (List.map (\child -> li [] [ child ]) children) ] , div [ class "ml-4 mb-4" ] - [ viewAddCategory translators [ class "w-full" ] model (Just category) + [ viewAddCategory translators [ class "w-full" ] model category ] ] ] @@ -1372,28 +1409,20 @@ viewAddCategory : Translation.Translators -> List (Html.Attribute Msg) -> Model - -> Maybe Shop.Category.Model + -> Shop.Category.Model -> Html Msg -viewAddCategory translators attrs model maybeParentCategory = +viewAddCategory translators attrs model parentCategory = let - parentId = - Maybe.map .id maybeParentCategory - viewAddCategoryButton customAttrs = button (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm whitespace-nowrap focus:bg-orange-100/20 focus-ring" :: type_ "button" - :: onClick (ClickedAddCategory parentId) + :: onClick (ClickedAddCategory (Just parentCategory.id)) :: customAttrs ) [ span [ class "sticky left-2 flex items-center" ] [ Icons.plus "w-4 h-4 mr-2" - , case maybeParentCategory of - Nothing -> - text <| translators.t "shop.categories.add_root" - - Just { name } -> - text <| translators.tr "shop.categories.add_child" [ ( "parent_name", name ) ] + , text <| translators.tr "shop.categories.add_child" [ ( "parent_name", parentCategory.name ) ] ] ] in @@ -1402,32 +1431,37 @@ viewAddCategory translators attrs model maybeParentCategory = viewAddCategoryButton attrs EditingNewCategory newCategoryData -> - if newCategoryData.parent == parentId then - Form.view (class "border !border-gray-300 rounded-md p-4" :: attrs) - translators - (\submitButton -> - [ div [ class "flex flex-col sm:flex-row justify-end gap-4 mt-10" ] - [ button - [ class "button button-secondary w-full sm:w-40" - , type_ "button" - , onClick ClickedCancelAddCategory - ] - [ text <| translators.t "menu.cancel" ] - , submitButton [ class "button button-primary w-full sm:w-40" ] - [ text <| translators.t "menu.create" ] - ] - ] - ) - (newCategoryForm translators) - newCategoryData.form - { toMsg = GotAddCategoryFormMsg - , onSubmit = SubmittedAddCategoryForm - } + if newCategoryData.parent == Just parentCategory.id then + viewNewCategoryForm attrs translators newCategoryData.form else viewAddCategoryButton attrs +viewNewCategoryForm : List (Html.Attribute Msg) -> Translation.Translators -> Form.Model NewCategoryFormInput -> Html Msg +viewNewCategoryForm attrs translators formModel = + Form.view (class "border !border-gray-300 rounded-md p-4" :: attrs) + translators + (\submitButton -> + [ div [ class "flex flex-col sm:flex-row justify-end gap-4 mt-10" ] + [ button + [ class "button button-secondary w-full sm:w-40" + , type_ "button" + , onClick ClickedCancelAddCategory + ] + [ text <| translators.t "menu.cancel" ] + , submitButton [ class "button button-primary w-full sm:w-40" ] + [ text <| translators.t "menu.create" ] + ] + ] + ) + (newCategoryForm translators) + formModel + { toMsg = GotAddCategoryFormMsg + , onSubmit = SubmittedAddCategoryForm + } + + viewActions : Translation.Translators -> @@ -1484,10 +1518,6 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model ] [ Icons.edit "max-h-4 w-8" ] - , div - [ class "w-2" - ] - [] , button [ class "h-8 px-2 rounded-sm transition-colors action-opener focus-ring" , classList From a42ef2e651119c9576371c3123b95398aeb38605 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 21:17:26 -0300 Subject: [PATCH 080/104] Change text colors and weights --- src/elm/Page/Community/Settings/Shop/Categories.elm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 8fcf0937e..97afee3c8 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1372,7 +1372,7 @@ viewCategoryWithChildren translators model zipper children = Just icon -> img [ src icon, alt "", class "h-6 w-6 rounded-full mr-2" ] [] - , span [ class "whitespace-nowrap" ] + , span [ class "whitespace-nowrap font-bold text-black" ] [ text category.name ] ] @@ -1415,13 +1415,13 @@ viewAddCategory translators attrs model parentCategory = let viewAddCategoryButton customAttrs = button - (class "flex items-center px-2 h-8 font-bold transition-colors hover:bg-orange-100/20 rounded-sm whitespace-nowrap focus:bg-orange-100/20 focus-ring" + (class "flex items-center px-2 h-8 transition-colors hover:bg-orange-100/20 rounded-sm whitespace-nowrap focus:bg-orange-100/20 focus-ring" :: type_ "button" :: onClick (ClickedAddCategory (Just parentCategory.id)) :: customAttrs ) - [ span [ class "sticky left-2 flex items-center" ] - [ Icons.plus "w-4 h-4 mr-2" + [ span [ class "sticky left-2 flex items-center transition-colors text-orange-500/70 grand-parent-2-hover:text-orange-500/90" ] + [ Icons.plus "w-4 h-4 mr-2 fill-current" , text <| translators.tr "shop.categories.add_child" [ ( "parent_name", parentCategory.name ) ] ] ] From dfc9617f5d6ee23d003e9616a2ec2acf34f098d3 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 21:29:06 -0300 Subject: [PATCH 081/104] Add page description --- public/translations/amh-ETH.json | 1 + public/translations/cat-CAT.json | 1 + public/translations/en-US.json | 1 + public/translations/es-ES.json | 1 + public/translations/pt-BR.json | 1 + .../Community/Settings/Shop/Categories.elm | 21 +++++++++++-------- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index ebbec6f78..1addea8be 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -872,6 +872,7 @@ "description": "ለማህበረሰቢ ይህ ሱቁ አለተፈቀደም." }, "categories": { + "admin_page_description": "በሱቁ ውስጥ የሚገኙትን ምድቦች ያክሉ እና ያስተዳድሩ።", "empty": { "title": "የእርስዎ ማህበረሰብ ምንም አይነት ምድቦች ያለው አይመስልም!", "description": "ከዚህ በታች አዲስ ምድብ በመፍጠር ይጀምሩ" diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index a2efd32dd..940af427b 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -914,6 +914,7 @@ "description": "La botiga està desactivada en aquesta comunitat." }, "categories": { + "admin_page_description": "Afegeix i gestiona les categories que estaran disponibles a la botiga.", "empty": { "title": "Sembla que la teva comunitat no té cap categoria!", "description": "Comenceu creant una categoria nova a continuació" diff --git a/public/translations/en-US.json b/public/translations/en-US.json index 0cdfe720e..09bd3035f 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -911,6 +911,7 @@ "description": "Shop is disabled in this community." }, "categories": { + "admin_page_description": "Add and manage the categories that will be available in the shop.", "empty": { "title": "Looks like your community doesn't have any categories!", "description": "Get started by creating a new category below" diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index 3a0319e95..f32394362 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -914,6 +914,7 @@ "description": "La tienda está deshabilitada en esta comunidad." }, "categories": { + "admin_page_description": "Añade y gestiona las categorías que estarán disponibles en la tienda.", "empty": { "title": "¡Parece que tu comunidad no tiene ninguna categoría!", "description": "Comience creando una nueva categoría abajo" diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index 1356b3cfd..31945d689 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -918,6 +918,7 @@ "description": "A loja está desativada nesta comunidade." }, "categories": { + "admin_page_description": "Adicione e gerencie as categories que ficarão disponíveis na loja.", "empty": { "title": "Parece que sua comunidade ainda não tem nenhuma categoria!", "description": "Comece criando uma nova categoria abaixo" diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 97afee3c8..bf8961086 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1032,10 +1032,10 @@ view loggedIn model = [ Page.viewHeader loggedIn title , case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.NotAsked -> - viewLoading model + viewLoading loggedIn.shared.translators model RemoteData.Loading -> - viewLoading model + viewLoading loggedIn.shared.translators model RemoteData.Failure fieldErr -> case fieldErr of @@ -1055,8 +1055,8 @@ view loggedIn model = } -viewPageContainer : { children : List (Html Msg), modals : List (Html Msg) } -> Model -> Html Msg -viewPageContainer { children, modals } model = +viewPageContainer : Translation.Translators -> { children : List (Html Msg), modals : List (Html Msg) } -> Model -> Html Msg +viewPageContainer translators { children, modals } model = div [ class "container mx-auto sm:px-4 sm:mt-6 pb-40 overflow-x-hidden" ] (div [ class "bg-white container mx-auto pt-6 pb-7 w-full px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" @@ -1065,20 +1065,23 @@ viewPageContainer { children, modals } model = , ( "overflow-y-visible", Maybe.Extra.isJust model.actionsDropdown ) ] ] - children + (p [ class "text-gray-900 mb-10" ] + [ text <| translators.t "shop.categories.admin_page_description" ] + :: children + ) :: modals ) -viewLoading : Model -> Html Msg -viewLoading model = +viewLoading : Translation.Translators -> Model -> Html Msg +viewLoading translators model = let viewBar : List (Html.Attribute Msg) -> Html Msg viewBar attributes = div (class "animate-skeleton-loading max-w-full h-8 rounded-sm mt-2" :: attributes) [] in - viewPageContainer + viewPageContainer translators { modals = [] , children = [ viewBar [ class "mt-0" ] @@ -1133,7 +1136,7 @@ view_ translators community model categories = else Nothing in - viewPageContainer + viewPageContainer translators { children = [ case categories of [] -> From e8eb699a1615bd647a37003dba7fd89911f3f4a6 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 21:51:31 -0300 Subject: [PATCH 082/104] Dont show modal header and body text while loading file type --- src/elm/Form/File.elm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/elm/Form/File.elm b/src/elm/Form/File.elm index 06fea70fd..7478ad12f 100644 --- a/src/elm/Form/File.elm +++ b/src/elm/Form/File.elm @@ -1358,10 +1358,15 @@ viewEntryModal (Options options) viewConfig { isVisible, index } entry toMsg = , translators.t "form.file.body_file" ) - _ -> + LoadedFileType Pdf -> ( translators.t "form.file.edit_file" , translators.t "form.file.body_file" ) + + LoadingFileType -> + ( "" + , "" + ) in Modal.initWith { closeMsg = toMsg ClickedCloseEntryModal From 50156e7a54d9b1924e228ad1753c2a75ded05487 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 27 Jun 2022 21:51:38 -0300 Subject: [PATCH 083/104] Focus icon input instead of name --- src/elm/Page/Community/Settings/Shop/Categories.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index bf8961086..0417b5ea1 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -220,7 +220,7 @@ update msg model loggedIn = } |> UR.init |> UR.addCmd - (Browser.Dom.focus "new-category-name" + (Browser.Dom.focus "new-category-icon" |> Task.attempt (\_ -> NoOp) ) From d8695b316f40d2d221bf609c7f73da84a78369ff Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 28 Jun 2022 16:55:38 -0300 Subject: [PATCH 084/104] Show menu on right click --- .../Community/Settings/Shop/Categories.elm | 171 ++++++++++++++---- 1 file changed, 134 insertions(+), 37 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 0417b5ea1..1cff75b67 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -22,8 +22,8 @@ import Form.Text import Form.Validate import Graphql.Http import Graphql.SelectionSet -import Html exposing (Html, button, details, div, h2, img, li, p, span, summary, text, ul) -import Html.Attributes exposing (alt, class, classList, disabled, id, src, type_) +import Html exposing (Html, button, details, div, h2, img, li, menu, p, span, summary, text, ul) +import Html.Attributes exposing (alt, class, classList, disabled, id, src, style, type_) import Html.Attributes.Aria exposing (ariaHasPopup, ariaHidden, ariaLabel) import Html.Events exposing (onClick) import Icons @@ -58,13 +58,19 @@ type alias Model = , newCategoryState : NewCategoryState , categoryModalState : CategoryFormState UpdateCategoryFormInput , categoryMetadataModalState : CategoryFormState MetadataFormInput - , actionsDropdown : Maybe Shop.Category.Id + , actionsDropdown : DropdownState , askingForDeleteConfirmation : Maybe Shop.Category.Id , deleting : EverySet Shop.Category.Id , dnd : Dnd.Model Shop.Category.Id DropZone } +type DropdownState + = DropdownClosed + | DropdownOpenOnMouse { x : Float, y : Float } Shop.Category.Id + | DropdownOpenOnButton Shop.Category.Id + + type DropZone = OnTopOf Shop.Category.Id | OnRoot @@ -76,7 +82,7 @@ init _ = , newCategoryState = NotEditing , categoryModalState = Closed , categoryMetadataModalState = Closed - , actionsDropdown = Nothing + , actionsDropdown = DropdownClosed , askingForDeleteConfirmation = Nothing , deleting = EverySet.empty , dnd = Dnd.init @@ -121,6 +127,7 @@ type Msg | ConfirmedDeleteCategory Shop.Category.Id | CompletedDeletingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) | ClickedShowActionsDropdown Shop.Category.Id + | OpenedContextMenuForAction { x : Float, y : Float } Shop.Category.Id | ClosedActionsDropdown | ClickedOpenMetadataModal Shop.Category.Id | GotMetadataFormMsg (Form.Msg MetadataFormInput) @@ -167,7 +174,7 @@ update msg model loggedIn = { model | newCategoryState = NotEditing , categoryModalState = Closed - , actionsDropdown = Nothing + , actionsDropdown = DropdownClosed , askingForDeleteConfirmation = Nothing } |> UR.init @@ -252,12 +259,12 @@ update msg model loggedIn = } } ) - , actionsDropdown = Nothing + , actionsDropdown = DropdownClosed } |> UR.init Nothing -> - { model | actionsDropdown = Nothing } + { model | actionsDropdown = DropdownClosed } |> UR.init ClosedCategoryModal -> @@ -568,16 +575,35 @@ update msg model loggedIn = ClickedShowActionsDropdown categoryId -> { model | actionsDropdown = - if model.actionsDropdown == Just categoryId then - Nothing + case model.actionsDropdown of + DropdownClosed -> + DropdownOpenOnButton categoryId - else - Just categoryId + DropdownOpenOnButton _ -> + DropdownClosed + + DropdownOpenOnMouse _ _ -> + DropdownOpenOnButton categoryId + } + |> UR.init + + OpenedContextMenuForAction coordinates categoryId -> + { model + | actionsDropdown = + case model.actionsDropdown of + DropdownClosed -> + DropdownOpenOnMouse coordinates categoryId + + DropdownOpenOnButton _ -> + DropdownOpenOnMouse coordinates categoryId + + DropdownOpenOnMouse _ _ -> + DropdownClosed } |> UR.init ClosedActionsDropdown -> - { model | actionsDropdown = Nothing } + { model | actionsDropdown = DropdownClosed } |> UR.init ClickedOpenMetadataModal categoryId -> @@ -1057,12 +1083,24 @@ view loggedIn model = viewPageContainer : Translation.Translators -> { children : List (Html Msg), modals : List (Html Msg) } -> Model -> Html Msg viewPageContainer translators { children, modals } model = + let + isActionsDropdownOpen = + case model.actionsDropdown of + DropdownClosed -> + False + + DropdownOpenOnButton _ -> + True + + DropdownOpenOnMouse _ _ -> + True + in div [ class "container mx-auto sm:px-4 sm:mt-6 pb-40 overflow-x-hidden" ] (div [ class "bg-white container mx-auto pt-6 pb-7 w-full px-4 sm:px-6 sm:rounded sm:shadow-lg lg:w-2/3" , classList - [ ( "overflow-x-scroll", Maybe.Extra.isNothing model.actionsDropdown ) - , ( "overflow-y-visible", Maybe.Extra.isJust model.actionsDropdown ) + [ ( "overflow-x-scroll", not isActionsDropdownOpen ) + , ( "overflow-y-visible", isActionsDropdownOpen ) ] ] (p [ class "text-gray-900 mb-10" ] @@ -1282,10 +1320,15 @@ viewCategoryWithChildren translators model zipper children = hasActionsMenuOpen = case model.actionsDropdown of - Nothing -> + DropdownClosed -> False - Just actionsDropdown -> + DropdownOpenOnButton actionsDropdown -> + isAncestorOf + actionsDropdown + (Tree.Zipper.tree zipper) + + DropdownOpenOnMouse _ actionsDropdown -> isAncestorOf actionsDropdown (Tree.Zipper.tree zipper) @@ -1365,6 +1408,21 @@ viewCategoryWithChildren translators model zipper children = , ( "bg-orange-100/20", hasActionsMenuOpen ) ] :: onClick (ClickedToggleExpandCategory category.id) + :: Html.Events.preventDefaultOn "contextmenu" + (Json.Decode.map2 + (\x y -> + ( OpenedContextMenuForAction { x = x, y = y } category.id + , case model.actionsDropdown of + DropdownOpenOnMouse _ _ -> + False + + _ -> + True + ) + ) + (Json.Decode.field "clientX" Json.Decode.float) + (Json.Decode.field "clientY" Json.Decode.float) + ) :: Dnd.draggable category.id GotDndMsg ) [ div [ class "flex items-center sticky left-0 w-full" ] @@ -1383,7 +1441,17 @@ viewCategoryWithChildren translators model zipper children = [ class "sticky right-0 bg-white rounded-md transition-color" , classList [ ( "bg-transparent", isDraggingSomething ) - , ( "z-10", model.actionsDropdown == Just category.id ) + , ( "z-10" + , case model.actionsDropdown of + DropdownClosed -> + False + + DropdownOpenOnButton actionsDropdown -> + actionsDropdown == category.id + + DropdownOpenOnMouse _ actionsDropdown -> + actionsDropdown == category.id + ) ] ] [ viewActions translators @@ -1481,10 +1549,13 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model isDropdownOpen = case model.actionsDropdown of - Nothing -> + DropdownClosed -> False - Just actionsDropdown -> + DropdownOpenOnButton actionsDropdown -> + actionsDropdown == category.id + + DropdownOpenOnMouse _ actionsDropdown -> actionsDropdown == category.id canGoDown = @@ -1537,9 +1608,25 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model text "" else - ul - [ class "absolute right-0 top-full bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm" - ] + let + openOnMouseAttrs = + case model.actionsDropdown of + DropdownOpenOnMouse { x, y } _ -> + [ style "left" (String.fromFloat x ++ "px") + , style "top" (String.fromFloat y ++ "px") + , class "fixed" + ] + + _ -> + [] + in + menu + (class "bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm marker-hidden" + :: classList + [ ( "absolute right-0 top-full", model.actionsDropdown == DropdownOpenOnButton category.id ) + ] + :: openOnMouseAttrs + ) [ li [] [ viewAction [ class "sm:hidden" ] { icon = Icons.edit "w-4 ml-1 mr-3" @@ -2146,27 +2233,34 @@ nameAndSlugForm translators { nameFieldId } = subscriptions : Model -> Sub Msg subscriptions model = + let + closeDropdownSubscription = + Browser.Events.onClick + (Json.Decode.oneOf + [ Json.Decode.at [ "target", "className" ] Json.Decode.string + |> Json.Decode.andThen + (\targetClass -> + if String.contains "action-opener" targetClass then + Json.Decode.fail "" + + else + Json.Decode.succeed ClosedActionsDropdown + ) + , Json.Decode.succeed ClosedActionsDropdown + ] + ) + in Sub.batch [ Utils.escSubscription PressedEsc , case model.actionsDropdown of - Nothing -> + DropdownClosed -> Sub.none - Just _ -> - Browser.Events.onClick - (Json.Decode.oneOf - [ Json.Decode.at [ "target", "className" ] Json.Decode.string - |> Json.Decode.andThen - (\targetClass -> - if String.contains "action-opener" targetClass then - Json.Decode.fail "" + DropdownOpenOnButton _ -> + closeDropdownSubscription - else - Json.Decode.succeed ClosedActionsDropdown - ) - , Json.Decode.succeed ClosedActionsDropdown - ] - ) + DropdownOpenOnMouse _ _ -> + closeDropdownSubscription ] @@ -2318,6 +2412,9 @@ msgToString msg = ClickedShowActionsDropdown _ -> [ "ClickedShowActionsDropdown" ] + OpenedContextMenuForAction _ _ -> + [ "OpenedContextMenuForAction" ] + ClosedActionsDropdown -> [ "ClosedActionsDropdown" ] From 52dee34ab48c54ad4103aec9d92500fb17bd4565 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 28 Jun 2022 17:00:44 -0300 Subject: [PATCH 085/104] Only send meta title and description if they've been altered --- .../Community/Settings/Shop/Categories.elm | 20 ++++++++++++++++--- src/elm/Shop/Category.elm | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 1cff75b67..4dac70287 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -649,6 +649,10 @@ update msg model loggedIn = UR.init model Just zipper -> + let + category = + Tree.Zipper.label zipper + in { model | categoryMetadataModalState = case model.categoryMetadataModalState of @@ -661,9 +665,19 @@ update msg model loggedIn = |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn - (Shop.Category.updateMetadata (Tree.Zipper.label zipper) - { metaTitle = formOutput.metaTitle - , metaDescription = formOutput.metaDescription + (Shop.Category.updateMetadata category + { metaTitle = + if Maybe.Extra.isNothing category.metaTitle && formOutput.metaTitle == category.name then + Nothing + + else + Just formOutput.metaTitle + , metaDescription = + if Maybe.Extra.isNothing category.metaDescription && formOutput.metaDescription == Markdown.toUnformattedString category.description then + Nothing + + else + Just formOutput.metaDescription , metaKeywords = formOutput.metaKeywords } Shop.Category.selectionSet diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index a8f522373..aa259efa1 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -100,7 +100,7 @@ update model { icon, name, description, slug, image } = updateMetadata : Model - -> { metaTitle : String, metaDescription : String, metaKeywords : List String } + -> { metaTitle : Maybe String, metaDescription : Maybe String, metaKeywords : List String } -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation updateMetadata model { metaTitle, metaDescription, metaKeywords } = @@ -108,9 +108,9 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = (\optionals -> { optionals | id = OptionalArgument.Present (unwrapId model.id) - , metaDescription = OptionalArgument.Present metaDescription + , metaDescription = OptionalArgument.fromMaybe metaDescription , metaKeywords = OptionalArgument.Present (String.join ", " metaKeywords) - , metaTitle = OptionalArgument.Present metaTitle + , metaTitle = OptionalArgument.fromMaybe metaTitle } ) From ce2efe6340c06fe221f147b0582378b906eff4e6 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 28 Jun 2022 17:07:39 -0300 Subject: [PATCH 086/104] Change Slug -> Sharing URL --- public/translations/amh-ETH.json | 8 ++++---- public/translations/cat-CAT.json | 8 ++++---- public/translations/en-US.json | 8 ++++---- public/translations/es-ES.json | 8 ++++---- public/translations/pt-BR.json | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index 1addea8be..2bfa65b4e 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -883,7 +883,7 @@ "actions_for": "እርምጃዎች ለ {{category_name}}", "fields": { "name": "ስም", - "slug": "ስሉግ", + "slug": "URL አጋራ", "description": "መግለጫ", "image": "ምስል", "meta": { @@ -893,9 +893,9 @@ } }, "form": { - "insert_name": "ስሉግን ለማመንጨት ስም ያስገቡ", - "invalid_slug": "ልክ ያልሆነ ሸርተቴ", - "invalid_slug_tooltip": "የላቲን ያልሆኑ ገጸ-ባህሪያት በስሎግስ ውስጥ ተቀባይነት የላቸውም። የሚሰራ ስሉግ ለማመንጨት የምድብ ስም ቢያንስ አንድ የላቲን ቁምፊ ሊኖረው ይገባል" + "insert_name": "የተጋራ ዩአርኤል ለማመንጨት ስም ያስገቡ", + "invalid_slug": "ልክ ያልሆነ የማጋራት URL", + "invalid_slug_tooltip": "የላቲን ያልሆኑ ቁምፊዎች በዩአርኤሎች ውስጥ ተቀባይነት የላቸውም። የሚሰራ ዩአርኤል ለማመንጨት የምድብ ስሙ ቢያንስ አንድ የላቲን ቁምፊ ሊኖረው ይገባል።" }, "metadata": { "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 940af427b..41055eec6 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -925,7 +925,7 @@ "actions_for": "Accions per a {{category_name}}", "fields": { "name": "Nom", - "slug": "Slug", + "slug": "URL Compartit", "description": "Descripció", "image": "Imatge", "meta": { @@ -935,9 +935,9 @@ } }, "form": { - "insert_name": "Insereix un nom per generar el slug", - "invalid_slug": "Slug no vàlid", - "invalid_slug_tooltip": "Els caràcters no llatins no s'accepten als slugs. Per generar un slug vàlid, el nom de la categoria ha de tenir almenys un caràcter llatí" + "insert_name": "Introduïu un nom per generar l'URL compartit", + "invalid_slug": "URL compartit no vàlid", + "invalid_slug_tooltip": "Els caràcters no llatins no s'accepten als URLs. Per generar un URL vàlid, el nom de la categoria ha de tenir almenys un caràcter llatí" }, "metadata": { "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", diff --git a/public/translations/en-US.json b/public/translations/en-US.json index 09bd3035f..da72b32cb 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -922,7 +922,7 @@ "actions_for": "Actions for {{category_name}}", "fields": { "name": "Name", - "slug": "Slug", + "slug": "Sharing URL", "description": "Description", "image": "Image", "meta": { @@ -932,9 +932,9 @@ } }, "form": { - "insert_name": "Insert a name to generate the slug", - "invalid_slug": "Invalid slug", - "invalid_slug_tooltip": "Non-latin characters aren't accepted in slugs. In order to generate a valid slug, the category name must have at least one latin character" + "insert_name": "Insert a name to generate the sharing URL", + "invalid_slug": "Invalid sharing URL", + "invalid_slug_tooltip": "Non-latin characters aren't accepted in URLs. In order to generate a valid URL, the category name must have at least one latin character" }, "metadata": { "guidance": "This information will be used to display rich links when sharing this category.", diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index f32394362..e84ce4e01 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -925,7 +925,7 @@ "actions_for": "Acciones para {{category_name}}", "fields": { "name": "Nombre", - "slug": "Slug", + "slug": "URL para Compartir", "description": "Descripción", "image": "Imagen", "meta": { @@ -935,9 +935,9 @@ } }, "form": { - "insert_name": "Inserta un nombre para generar el slug", - "invalid_slug": "Slug no válido", - "invalid_slug_tooltip": "Los caracteres no latinos no se aceptan en slugs. Para generar un slug válido, el nombre de la categoría debe tener al menos un carácter latino" + "insert_name": "Inserta un nombre para generar la URL para compartir", + "invalid_slug": "URL para compartir no válida", + "invalid_slug_tooltip": "Los caracteres no latinos no se aceptan en URLs. Para generar un URL válido, el nombre de la categoría debe tener al menos un carácter latino" }, "metadata": { "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index 31945d689..eb046d2e3 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -929,7 +929,7 @@ "actions_for": "Ações para {{category_name}}", "fields": { "name": "Nome", - "slug": "Slug", + "slug": "URL de Compartilhamento", "description": "Descrição", "image": "Imagem", "meta": { @@ -939,9 +939,9 @@ } }, "form": { - "insert_name": "Insira um nome para gerar o slug", - "invalid_slug": "Slug inválido", - "invalid_slug_tooltip": "Carácteres não-latinos não são aceitos em slugs. Para gerar um slug válido, o nome da categoria deve ter ao menos um caráctere latim" + "insert_name": "Insira um nome para gerar a URL de compartilhamento", + "invalid_slug": "URL de compartilhamento inválida", + "invalid_slug_tooltip": "Caracteres não-latinos não são aceitos em URLs. Para gerar um URL válido, o nome da categoria deve ter ao menos um caractere latim" }, "metadata": { "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", From d4c287b14ffdefa489a910a34047cde20b9b52c9 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 28 Jun 2022 17:19:12 -0300 Subject: [PATCH 087/104] Adjust root button size --- src/elm/Page/Community/Settings/Shop/Categories.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 4dac70287..30cc24d39 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1237,7 +1237,7 @@ view_ translators community model categories = , case maybeNewRootCategoryForm of Nothing -> button - [ class "button button-primary w-full sticky left-0 mt-8" + [ class "button button-primary w-full sm:w-auto whitespace-nowrap sm:px-4 sticky left-0 mt-8" , onClick (ClickedAddCategory Nothing) ] [ text <| translators.t "shop.categories.add_root" ] From 45baaa722ac85a9733eb3e864cf0fef007707dde Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 28 Jun 2022 17:24:19 -0300 Subject: [PATCH 088/104] Only change bg when open on button --- .../Community/Settings/Shop/Categories.elm | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 30cc24d39..95a4ee5fd 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1572,6 +1572,32 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model DropdownOpenOnMouse _ actionsDropdown -> actionsDropdown == category.id + isDropdownOpenOnButton = + case model.actionsDropdown of + DropdownClosed -> + False + + DropdownOpenOnButton actionsDropdown -> + actionsDropdown == category.id + + DropdownOpenOnMouse _ actionsDropdown -> + False + + hasActionsMenuOpen = + case model.actionsDropdown of + DropdownClosed -> + False + + DropdownOpenOnButton actionsDropdown -> + isAncestorOf + actionsDropdown + (Tree.Zipper.tree zipper) + + DropdownOpenOnMouse _ actionsDropdown -> + isAncestorOf + actionsDropdown + (Tree.Zipper.tree zipper) + canGoDown = not (Maybe.Extra.isNothing (goDownWithoutChildren zipper) @@ -1595,7 +1621,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model , classList [ ( "bg-green/20", isParentOfNewCategoryForm ) , ( "grand-parent-1-focus:bg-orange-100/20 grand-parent-2-hover:bg-orange-100/20", not isParentOfNewCategoryForm && not isDraggingSomething ) - , ( "bg-orange-100/20", isDropdownOpen ) + , ( "bg-orange-100/20", hasActionsMenuOpen ) ] ] [ button @@ -1609,8 +1635,8 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model , button [ class "h-8 px-2 rounded-sm transition-colors action-opener focus-ring" , classList - [ ( "bg-orange-300/60", isDropdownOpen && not isParentOfNewCategoryForm ) - , ( "bg-green/30", isDropdownOpen && isParentOfNewCategoryForm ) + [ ( "bg-orange-300/60", isDropdownOpenOnButton && not isParentOfNewCategoryForm ) + , ( "bg-green/30", isDropdownOpenOnButton && isParentOfNewCategoryForm ) ] , buttonClassListsFromParent , Utils.onClickNoBubble (ClickedShowActionsDropdown category.id) From 485bd48b871cc9d53af475f6ed358fdd3761b2f6 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 17:56:45 -0300 Subject: [PATCH 089/104] Add description on image input --- public/translations/amh-ETH.json | 3 ++- public/translations/cat-CAT.json | 3 ++- public/translations/en-US.json | 3 ++- public/translations/es-ES.json | 3 ++- public/translations/pt-BR.json | 3 ++- src/elm/Page/Community/Settings/Shop/Categories.elm | 6 ++++++ 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index 2bfa65b4e..927baf3d0 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -895,7 +895,8 @@ "form": { "insert_name": "የተጋራ ዩአርኤል ለማመንጨት ስም ያስገቡ", "invalid_slug": "ልክ ያልሆነ የማጋራት URL", - "invalid_slug_tooltip": "የላቲን ያልሆኑ ቁምፊዎች በዩአርኤሎች ውስጥ ተቀባይነት የላቸውም። የሚሰራ ዩአርኤል ለማመንጨት የምድብ ስሙ ቢያንስ አንድ የላቲን ቁምፊ ሊኖረው ይገባል።" + "invalid_slug_tooltip": "የላቲን ያልሆኑ ቁምፊዎች በዩአርኤሎች ውስጥ ተቀባይነት የላቸውም። የሚሰራ ዩአርኤል ለማመንጨት የምድብ ስሙ ቢያንስ አንድ የላቲን ቁምፊ ሊኖረው ይገባል።", + "image_guidance": "ምስሎችን በከፍተኛ ጥራት በወርድ ሁነታ ይምረጡ" }, "metadata": { "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 41055eec6..2387deaa3 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -937,7 +937,8 @@ "form": { "insert_name": "Introduïu un nom per generar l'URL compartit", "invalid_slug": "URL compartit no vàlid", - "invalid_slug_tooltip": "Els caràcters no llatins no s'accepten als URLs. Per generar un URL vàlid, el nom de la categoria ha de tenir almenys un caràcter llatí" + "invalid_slug_tooltip": "Els caràcters no llatins no s'accepten als URLs. Per generar un URL vàlid, el nom de la categoria ha de tenir almenys un caràcter llatí", + "image_guidance": "Preferiu imatges en mode horitzontal amb alta qualitat" }, "metadata": { "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", diff --git a/public/translations/en-US.json b/public/translations/en-US.json index da72b32cb..18f2a3a64 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -934,7 +934,8 @@ "form": { "insert_name": "Insert a name to generate the sharing URL", "invalid_slug": "Invalid sharing URL", - "invalid_slug_tooltip": "Non-latin characters aren't accepted in URLs. In order to generate a valid URL, the category name must have at least one latin character" + "invalid_slug_tooltip": "Non-latin characters aren't accepted in URLs. In order to generate a valid URL, the category name must have at least one latin character", + "image_guidance": "Prefer images in landscape mode with high quality" }, "metadata": { "guidance": "This information will be used to display rich links when sharing this category.", diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index e84ce4e01..9864c3188 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -937,7 +937,8 @@ "form": { "insert_name": "Inserta un nombre para generar la URL para compartir", "invalid_slug": "URL para compartir no válida", - "invalid_slug_tooltip": "Los caracteres no latinos no se aceptan en URLs. Para generar un URL válido, el nombre de la categoría debe tener al menos un carácter latino" + "invalid_slug_tooltip": "Los caracteres no latinos no se aceptan en URLs. Para generar un URL válido, el nombre de la categoría debe tener al menos un carácter latino", + "image_guidance": "Prefiere imágenes en modo horizontal con alta calidad" }, "metadata": { "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index eb046d2e3..ab7985dd3 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -941,7 +941,8 @@ "form": { "insert_name": "Insira um nome para gerar a URL de compartilhamento", "invalid_slug": "URL de compartilhamento inválida", - "invalid_slug_tooltip": "Caracteres não-latinos não são aceitos em URLs. Para gerar um URL válido, o nome da categoria deve ter ao menos um caractere latim" + "invalid_slug_tooltip": "Caracteres não-latinos não são aceitos em URLs. Para gerar um URL válido, o nome da categoria deve ter ao menos um caractere latim", + "image_guidance": "Prefira imagens em modo paisagem com alta qualidade" }, "metadata": { "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 95a4ee5fd..b3f3eeaf2 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -2013,6 +2013,12 @@ imageForm translators fieldId = , externalError = always Nothing } |> Form.optional + |> Form.withNoOutput + (Form.arbitrary + (p [ class "mt-2 text-gray-900" ] + [ text <| translators.t "shop.categories.form.image_guidance" ] + ) + ) updateCategoryForm : Translation.Translators -> Shop.Category.Id -> Form.Form msg UpdateCategoryFormInput UpdateCategoryFormOutput From 4dd033fbc5f922e01b3fce9aaac3d54433bd0684 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 17:59:27 -0300 Subject: [PATCH 090/104] Use different ellipsis icon, adjust icon sizes --- src/elm/Icons.elm | 4 ++-- src/elm/Page/Community/Settings/Shop/Categories.elm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/elm/Icons.elm b/src/elm/Icons.elm index abccb9846..16fbcd07c 100644 --- a/src/elm/Icons.elm +++ b/src/elm/Icons.elm @@ -326,8 +326,8 @@ edit class_ = ellipsis : String -> Svg msg ellipsis class_ = - svg [ width "32", height "32", viewBox "0 0 32 32", fill "none", class class_ ] - [ Svg.path [ d "M4 20C6.20934 20 8 18.2089 8 16C8 13.7907 6.20934 12 4 12C1.79066 12 0 13.7907 0 16C0 18.2089 1.79066 20 4 20Z", fill "currentColor" ] [], Svg.path [ d "M28 20C30.2089 20 32 18.2091 32 16C32 13.7909 30.2089 12 28 12C25.7907 12 24 13.7909 24 16C24 18.2091 25.7907 20 28 20Z", fill "currentColor" ] [], Svg.path [ d "M16 20C18.2089 20 20 18.2089 20 16C20 13.7907 18.2089 12 16 12C13.7907 12 12 13.7907 12 16C12 18.2089 13.7907 20 16 20Z", fill "currentColor" ] [] ] + svg [ width "16", height "16", viewBox "0 0 16 16", fill "none", class class_ ] + [ Svg.path [ d "M7.33334 8.00001C7.33334 8.3682 7.63182 8.66668 8.00001 8.66668C8.36819 8.66668 8.66667 8.3682 8.66667 8.00001C8.66667 7.63182 8.36819 7.33334 8.00001 7.33334C7.63182 7.33334 7.33334 7.63182 7.33334 8.00001Z", fill "currentColor", stroke "currentColor", strokeWidth "1.75", strokeLinecap "round", strokeLinejoin "round" ] [], Svg.path [ d "M2.66667 8.00001C2.66667 8.3682 2.96514 8.66668 3.33333 8.66668C3.70152 8.66668 4 8.3682 4 8.00001C4 7.63182 3.70152 7.33334 3.33333 7.33334C2.96514 7.33334 2.66667 7.63182 2.66667 8.00001Z", fill "currentColor", stroke "currentColor", strokeWidth "1.75", strokeLinecap "round", strokeLinejoin "round" ] [], Svg.path [ d "M12 8.00001C12 8.3682 12.2985 8.66668 12.6667 8.66668C13.0349 8.66668 13.3333 8.3682 13.3333 8.00001C13.3333 7.63182 13.0349 7.33334 12.6667 7.33334C12.2985 7.33334 12 7.63182 12 8.00001Z", fill "currentColor", stroke "currentColor", strokeWidth "1.75", strokeLinecap "round", strokeLinejoin "round" ] [] ] settings : String -> Svg msg diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index b3f3eeaf2..43227a3d1 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1630,7 +1630,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model , buttonClassListsFromParent , ariaLabel (translators.tr "shop.categories.click_category_to_edit" [ ( "category_name", category.name ) ]) ] - [ Icons.edit "max-h-4 w-8" + [ Icons.edit "max-h-4 w-4" ] , button [ class "h-8 px-2 rounded-sm transition-colors action-opener focus-ring" @@ -1643,7 +1643,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model , ariaHasPopup "true" , ariaLabel (translators.tr "shop.categories.action_for" [ ( "category_name", category.name ) ]) ] - [ Icons.ellipsis "h-4 pointer-events-none text-gray-800" ] + [ Icons.ellipsis "h-4 w-4 pointer-events-none text-gray-800" ] , if not isDropdownOpen then text "" From 8e3427bb671e8609fab2d6285586a361fa7a4442 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 18:04:53 -0300 Subject: [PATCH 091/104] Consider accentuated characters in slugs --- review/src/ReviewConfig.elm | 1 + .../Community/Settings/Shop/Categories.elm | 4 ++-- src/elm/Utils.elm | 20 +++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/review/src/ReviewConfig.elm b/review/src/ReviewConfig.elm index 585f46471..c35637d0f 100644 --- a/review/src/ReviewConfig.elm +++ b/review/src/ReviewConfig.elm @@ -91,6 +91,7 @@ config = , ( [ "View.Select.newConfig", "View.Select.view", "View.Select.update" ] , [ "Form.UserPicker" ] ) + , ( [ "Slug.generate" ], [ "Utils" ] ) ] ] -- Ignore generated code diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 43227a3d1..78341c303 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -2218,7 +2218,7 @@ nameAndSlugForm translators { nameFieldId } = >> Form.Validate.stringLongerThan 2 >> Form.Validate.custom (\name -> - case Slug.generate name of + case Utils.slugify name of Just _ -> Ok name @@ -2234,7 +2234,7 @@ nameAndSlugForm translators { nameFieldId } = ) |> Form.with ((\{ name } -> - case Slug.generate name of + case Utils.slugify name of Nothing -> Form.arbitrary (div [ class "mb-10" ] diff --git a/src/elm/Utils.elm b/src/elm/Utils.elm index 014f4aeba..a407d8555 100755 --- a/src/elm/Utils.elm +++ b/src/elm/Utils.elm @@ -12,6 +12,7 @@ module Utils exposing , onSubmitPreventAll , padInt , previousDay + , slugify , spawnMessage ) @@ -23,6 +24,7 @@ import Html.Events import Iso8601 import Json.Decode as Decode import Mask +import Slug import Task import Time exposing (Posix) @@ -169,3 +171,21 @@ spawnMessage : msg -> Cmd msg spawnMessage msg = Task.succeed msg |> Task.perform identity + + +{-| Try to turn a String into a Slug, replacing some accentuated characters. +Use this instead of `Slug.generate`, so we can have better slugs! +-} +slugify : String -> Maybe Slug.Slug +slugify input = + input + |> String.replace "á" "a" + |> String.replace "ã" "a" + |> String.replace "à" "a" + |> String.replace "ç" "c" + |> String.replace "é" "e" + |> String.replace "í" "i" + |> String.replace "ñ" "n" + |> String.replace "ó" "o" + |> String.replace "õ" "o" + |> Slug.generate From 69d9e7953e703a6ca574bdcf91a1f44e361fc014 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 18:13:22 -0300 Subject: [PATCH 092/104] Add elm-slug in elm-book --- elm-book/elm.json | 1 + 1 file changed, 1 insertion(+) diff --git a/elm-book/elm.json b/elm-book/elm.json index 20f1911fb..83142a925 100644 --- a/elm-book/elm.json +++ b/elm-book/elm.json @@ -31,6 +31,7 @@ "elm-community/maybe-extra": "5.2.1", "elm-explorations/test": "1.2.2", "fapian/elm-html-aria": "1.4.0", + "hecrj/elm-slug": "1.0.2", "justinmimbs/date": "3.2.1", "krisajenkins/remotedata": "6.0.1", "pablohirafuji/elm-syntax-highlight": "3.4.1", From 6e1fb70ce4408ab01aa0764b2c3dfae77b42d929 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 21:06:18 -0300 Subject: [PATCH 093/104] Update graphql types to include position --- src/elm/Cambiatus/InputObject.elm | 12 ++++++++---- src/elm/Cambiatus/Mutation.elm | 5 +++-- src/elm/Cambiatus/Object/Category.elm | 5 +++++ src/elm/Shop/Category.elm | 27 ++++++++++++++++++++------- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/elm/Cambiatus/InputObject.elm b/src/elm/Cambiatus/InputObject.elm index 1b29f5d78..d1e16ca98 100644 --- a/src/elm/Cambiatus/InputObject.elm +++ b/src/elm/Cambiatus/InputObject.elm @@ -424,17 +424,21 @@ buildSubcategoryInput : SubcategoryInputRequiredFields -> SubcategoryInput buildSubcategoryInput required = - { id = required.id } + { id = required.id, position = required.position } type alias SubcategoryInputRequiredFields = - { id : Int } + { id : Int + , position : Int + } {-| Type for the SubcategoryInput input object. -} type alias SubcategoryInput = - { id : Int } + { id : Int + , position : Int + } {-| Encode a SubcategoryInput into a value that can be used as an argument. @@ -442,7 +446,7 @@ type alias SubcategoryInput = encodeSubcategoryInput : SubcategoryInput -> Value encodeSubcategoryInput input = Encode.maybeObject - [ ( "id", Encode.int input.id |> Just ) ] + [ ( "id", Encode.int input.id |> Just ), ( "position", Encode.int input.position |> Just ) ] buildTransferDirection : diff --git a/src/elm/Cambiatus/Mutation.elm b/src/elm/Cambiatus/Mutation.elm index aee2d0744..01ba9e87e 100644 --- a/src/elm/Cambiatus/Mutation.elm +++ b/src/elm/Cambiatus/Mutation.elm @@ -58,6 +58,7 @@ type alias CategoryOptionalArguments = , metaTitle : OptionalArgument String , name : OptionalArgument String , parentId : OptionalArgument Int + , position : OptionalArgument Int , slug : OptionalArgument String } @@ -75,10 +76,10 @@ category : category fillInOptionals object_ = let filledInOptionals = - fillInOptionals { categories = Absent, description = Absent, iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, name = Absent, parentId = Absent, slug = Absent } + fillInOptionals { categories = Absent, description = Absent, iconUri = Absent, id = Absent, imageUri = Absent, metaDescription = Absent, metaKeywords = Absent, metaTitle = Absent, name = Absent, parentId = Absent, position = Absent, slug = Absent } optionalArgs = - [ Argument.optional "categories" filledInOptionals.categories (Cambiatus.InputObject.encodeSubcategoryInput |> Encode.list), Argument.optional "description" filledInOptionals.description Encode.string, Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "name" filledInOptionals.name Encode.string, Argument.optional "parentId" filledInOptionals.parentId Encode.int, Argument.optional "slug" filledInOptionals.slug Encode.string ] + [ Argument.optional "categories" filledInOptionals.categories (Cambiatus.InputObject.encodeSubcategoryInput |> Encode.list), Argument.optional "description" filledInOptionals.description Encode.string, Argument.optional "iconUri" filledInOptionals.iconUri Encode.string, Argument.optional "id" filledInOptionals.id Encode.int, Argument.optional "imageUri" filledInOptionals.imageUri Encode.string, Argument.optional "metaDescription" filledInOptionals.metaDescription Encode.string, Argument.optional "metaKeywords" filledInOptionals.metaKeywords Encode.string, Argument.optional "metaTitle" filledInOptionals.metaTitle Encode.string, Argument.optional "name" filledInOptionals.name Encode.string, Argument.optional "parentId" filledInOptionals.parentId Encode.int, Argument.optional "position" filledInOptionals.position Encode.int, Argument.optional "slug" filledInOptionals.slug Encode.string ] |> List.filterMap identity in Object.selectionForCompositeField "category" optionalArgs object_ (identity >> Decode.nullable) diff --git a/src/elm/Cambiatus/Object/Category.elm b/src/elm/Cambiatus/Object/Category.elm index 16439394f..5b6a699b0 100644 --- a/src/elm/Cambiatus/Object/Category.elm +++ b/src/elm/Cambiatus/Object/Category.elm @@ -78,6 +78,11 @@ parent object_ = Object.selectionForCompositeField "parent" [] object_ (identity >> Decode.nullable) +position : SelectionSet Int Cambiatus.Object.Category +position = + Object.selectionForField "Int" "position" [] Decode.int + + products : SelectionSet decodesTo Cambiatus.Object.Product -> SelectionSet (Maybe (List decodesTo)) Cambiatus.Object.Category diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index aa259efa1..f5a3536f6 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -43,6 +43,7 @@ type alias Model = , metaTitle : Maybe String , metaDescription : Maybe String , metaKeywords : List String + , position : Int } @@ -117,18 +118,28 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = addChild : Tree -> Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation addChild tree (Id newChildId) = + -- TODO - Accept (Maybe position) + let + toSubcategoryInput category = + { id = unwrapId category.id + , position = category.position + } + in Cambiatus.Mutation.category (\optionals -> { optionals | categories = Tree.children tree - |> List.map - (Tree.label - >> .id - >> unwrapId - >> (\id -> { id = id }) - ) - |> (\existingChildren -> existingChildren ++ [ { id = newChildId } ]) + |> List.map (Tree.label >> toSubcategoryInput) + |> (\existingChildren -> + existingChildren + ++ [ { id = newChildId + + -- TODO - Check this + , position = List.length existingChildren + } + ] + ) |> OptionalArgument.Present , id = Tree.label tree @@ -141,6 +152,7 @@ addChild tree (Id newChildId) = moveToRoot : Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation moveToRoot (Id id) = + -- TODO - Accept position Cambiatus.Mutation.category (\optionals -> { optionals @@ -177,6 +189,7 @@ selectionSet = >> Maybe.withDefault [] ) ) + |> SelectionSet.with Cambiatus.Object.Category.position From 20b662c28baf6984d245afca6a00665e0aa07401 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 21:17:09 -0300 Subject: [PATCH 094/104] Order incoming categories by their position --- src/elm/Shop/Category.elm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index f5a3536f6..70a91123d 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -221,6 +221,7 @@ treesSelectionSet categoriesSelectionSet = Nothing ) children + |> List.sortBy (Tree.label >> .position) ) in categoriesSelectionSet selectionSet @@ -235,6 +236,7 @@ treesSelectionSet categoriesSelectionSet = Nothing ) allCategories + |> List.sortBy (Tree.label >> .position) ) From d4cb1ef2f2ea95d864ad1b2ffc1f3f9a9d0b41e0 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 29 Jun 2022 23:40:35 -0300 Subject: [PATCH 095/104] Move children when clicking up and down --- .../Community/Settings/Shop/Categories.elm | 289 +++++++++++------- src/elm/Shop/Category.elm | 24 +- 2 files changed, 189 insertions(+), 124 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 78341c303..572fe650e 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -28,6 +28,7 @@ import Html.Attributes.Aria exposing (ariaHasPopup, ariaHidden, ariaLabel) import Html.Events exposing (onClick) import Icons import Json.Decode +import Json.Encode import List.Extra import Markdown exposing (Markdown) import Maybe.Extra @@ -135,7 +136,7 @@ type Msg | ClosedMetadataModal | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) | DraggedOverCategoryForAWhile Shop.Category.Id - | CompletedMovingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) + | CompletedMovingCategory Shop.Category.Id Int (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) | CompletedMovingCategoryToRoot Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) | ClickedMoveUp Shop.Category.Id | ClickedMoveDown Shop.Category.Id @@ -713,57 +714,45 @@ update msg model loggedIn = Just OnRoot -> UR.init model - CompletedMovingCategory categoryId (RemoteData.Success (Just parentId)) -> - case Community.getField loggedIn.selectedCommunity .shopCategories of - RemoteData.Success ( _, categories ) -> - case findInForest (\{ id } -> id == categoryId) categories of - Nothing -> - UR.init model - - Just childZipper -> - let - zipperWithMovedChild = - childZipper - |> Tree.Zipper.removeTree - |> Maybe.andThen - (Tree.Zipper.findFromRoot (\{ id } -> id == parentId) - >> Maybe.map - (Tree.Zipper.mapTree - (Tree.prependChild (Tree.Zipper.tree childZipper) - >> Tree.mapChildren (List.sortBy (Tree.label >> .name)) - ) - ) - ) - |> Maybe.withDefault childZipper - in - { model - | expandedCategories = - zipperWithMovedChild - |> Tree.Zipper.findFromRoot (\{ id } -> id == categoryId) - |> Maybe.map - (getAllAncestors - >> List.foldl - (Tree.Zipper.label - >> .id - >> EverySet.insert - ) - model.expandedCategories + CompletedMovingCategory categoryId position (RemoteData.Success (Just parentId)) -> + case getCategoryZipper categoryId of + Just childZipper -> + let + zipperWithMovedChild = + moveChild + { childZipper = childZipper + , newPosition = position + , parentId = parentId + } + in + { model + | expandedCategories = + zipperWithMovedChild + |> Tree.Zipper.findFromRoot (\{ id } -> id == categoryId) + |> Maybe.map + (getAllAncestors + >> List.foldl + (Tree.Zipper.label + >> .id + >> EverySet.insert ) - |> Maybe.withDefault model.expandedCategories - } - |> UR.init - |> UR.addExt - (zipperWithMovedChild - |> toFlatForest - |> Community.ShopCategories - |> LoggedIn.SetCommunityField + model.expandedCategories ) + |> Maybe.withDefault model.expandedCategories + } + |> UR.init + |> UR.addExt + (zipperWithMovedChild + |> toFlatForest + |> Community.ShopCategories + |> LoggedIn.SetCommunityField + ) - _ -> + Nothing -> model |> UR.init - CompletedMovingCategory categoryId (RemoteData.Success Nothing) -> + CompletedMovingCategory categoryId position (RemoteData.Success Nothing) -> UR.init model |> UR.logImpossible msg "Got Nothing when trying to move a child category" @@ -772,11 +761,15 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + , extras = + Dict.fromList + [ ( "id", Shop.Category.encodeId categoryId ) + , ( "position", Json.Encode.int position ) + ] } ] - CompletedMovingCategory categoryId (RemoteData.Failure err) -> + CompletedMovingCategory categoryId position (RemoteData.Failure err) -> UR.init model |> UR.logGraphqlError msg (Just loggedIn.accountName) @@ -785,13 +778,17 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + , extras = + Dict.fromList + [ ( "id", Shop.Category.encodeId categoryId ) + , ( "position", Json.Encode.int position ) + ] } ] err |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.reorder_error")) - CompletedMovingCategory _ _ -> + CompletedMovingCategory _ _ _ -> UR.init model CompletedMovingCategoryToRoot categoryId (RemoteData.Success (Just ())) -> @@ -803,13 +800,14 @@ update msg model loggedIn = Just childZipper -> let + -- TODO - Check this zipperWithChildOnRoot = childZipper |> Tree.Zipper.removeTree |> Maybe.map (toFlatForest >> (\forest -> Tree.Zipper.tree childZipper :: forest) - >> List.sortBy (Tree.label >> .name) + >> List.sortBy (Tree.label >> .position) ) |> Maybe.withDefault (toFlatForest childZipper) in @@ -856,73 +854,93 @@ update msg model loggedIn = UR.init model ClickedMoveUp categoryId -> - case Community.getField loggedIn.selectedCommunity .shopCategories of - RemoteData.Success ( _, categories ) -> - case findInForest (\{ id } -> id == categoryId) categories of + case getCategoryZipper categoryId of + Just zipper -> + case goUpWithoutChildren zipper of Nothing -> - UR.init model + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.moveToRoot categoryId + (Graphql.SelectionSet.succeed ()) + ) + (CompletedMovingCategoryToRoot categoryId) + ) - Just zipper -> - case goUpWithoutChildren zipper of - Nothing -> - model - |> UR.init - |> UR.addExt - (LoggedIn.mutation loggedIn - (Shop.Category.moveToRoot categoryId - (Graphql.SelectionSet.succeed ()) - ) - (CompletedMovingCategoryToRoot categoryId) - ) + Just newParentZipper -> + let + newPosition = + if Just newParentZipper == Tree.Zipper.previousSibling zipper then + Tree.Zipper.children newParentZipper + |> List.length - Just grandParentZipper -> - model - |> UR.init - |> UR.addExt - (LoggedIn.mutation loggedIn - (Shop.Category.addChild (Tree.Zipper.tree grandParentZipper) - categoryId - Shop.Category.idSelectionSet + else + Tree.Zipper.parent zipper + |> Maybe.map + (Tree.Zipper.label + >> .position + >> (\position -> position - 1) ) - (CompletedMovingCategory categoryId) - ) + |> Maybe.withDefault + (Tree.Zipper.children newParentZipper + |> List.length + ) + in + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.addChild (Tree.Zipper.tree newParentZipper) + categoryId + newPosition + Shop.Category.idSelectionSet + ) + (CompletedMovingCategory categoryId newPosition) + ) - _ -> + Nothing -> UR.init model ClickedMoveDown categoryId -> - case Community.getField loggedIn.selectedCommunity .shopCategories of - RemoteData.Success ( _, categories ) -> - case findInForest (\{ id } -> id == categoryId) categories of + case getCategoryZipper categoryId of + Just zipper -> + case goDownWithoutChildren zipper of Nothing -> - UR.init model + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.moveToRoot categoryId + (Graphql.SelectionSet.succeed ()) + ) + (CompletedMovingCategoryToRoot categoryId) + ) - Just zipper -> - case goDownWithoutChildren zipper of - Nothing -> - model - |> UR.init - |> UR.addExt - (LoggedIn.mutation loggedIn - (Shop.Category.moveToRoot categoryId - (Graphql.SelectionSet.succeed ()) - ) - (CompletedMovingCategoryToRoot categoryId) - ) + Just newParentZipper -> + let + newPosition = + if Just newParentZipper == Tree.Zipper.nextSibling zipper then + 0 - Just newParentZipper -> - model - |> UR.init - |> UR.addExt - (LoggedIn.mutation loggedIn - (Shop.Category.addChild (Tree.Zipper.tree newParentZipper) - categoryId - Shop.Category.idSelectionSet - ) - (CompletedMovingCategory categoryId) - ) + else + Tree.Zipper.parent zipper + |> Maybe.map (Tree.Zipper.label >> .position >> (+) 1) + |> Maybe.withDefault 0 + in + model + |> UR.init + |> UR.addExt + (LoggedIn.mutation loggedIn + (Shop.Category.addChild (Tree.Zipper.tree newParentZipper) + categoryId + newPosition + Shop.Category.idSelectionSet + ) + (CompletedMovingCategory categoryId newPosition) + ) - _ -> + Nothing -> UR.init model GotKeywordDndMsg subMsg -> @@ -1008,13 +1026,19 @@ updateDnd loggedIn ext ur = ur Just parentZipper -> + let + newPosition = + Tree.Zipper.children parentZipper + |> List.length + in UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.addChild (Tree.Zipper.tree parentZipper) draggedElement + newPosition Shop.Category.idSelectionSet ) - (CompletedMovingCategory draggedElement) + (CompletedMovingCategory draggedElement newPosition) ) ur @@ -2363,6 +2387,55 @@ goDownWithoutChildren zipper = Just firstSibling +moveChild : + { childZipper : Tree.Zipper.Zipper Shop.Category.Model + , newPosition : Int + , parentId : Shop.Category.Id + } + -> Tree.Zipper.Zipper Shop.Category.Model +moveChild { childZipper, newPosition, parentId } = + case Tree.Zipper.removeTree childZipper of + Nothing -> + childZipper + + Just zipperWithoutChild -> + case Tree.Zipper.findFromRoot (\{ id } -> id == parentId) zipperWithoutChild of + Nothing -> + childZipper + + Just newParentZipper -> + let + newChild = + Tree.Zipper.label childZipper + + incrementPosition childTree = + let + child = + Tree.label childTree + in + if child.position >= newPosition && child.id /= newChild.id then + Tree.replaceLabel { child | position = child.position + 1 } childTree + + else + childTree + in + Tree.Zipper.mapTree + (\parentTree -> + parentTree + |> Tree.prependChild + (Tree.Zipper.tree childZipper + |> Tree.mapLabel (\c -> { c | position = newPosition }) + ) + |> Tree.mapChildren + (\newChildren -> + newChildren + |> List.map incrementPosition + |> List.sortBy (Tree.label >> .position) + ) + ) + newParentZipper + + getAllAncestors : Tree.Zipper.Zipper a -> List (Tree.Zipper.Zipper a) getAllAncestors zipper = getAllAncestorsHelper zipper [] @@ -2482,7 +2555,7 @@ msgToString msg = DraggedOverCategoryForAWhile _ -> [ "DraggedOverCategoryForAWhile" ] - CompletedMovingCategory _ r -> + CompletedMovingCategory _ _ r -> [ "CompletedMovingCategory", UR.remoteDataToString r ] CompletedMovingCategoryToRoot _ r -> diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 70a91123d..a8523112c 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -116,13 +116,12 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = ) -addChild : Tree -> Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -addChild tree (Id newChildId) = - -- TODO - Accept (Maybe position) +addChild : Tree -> Id -> Int -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation +addChild tree newChildId position = let - toSubcategoryInput category = - { id = unwrapId category.id - , position = category.position + toSubcategoryInput index categoryId = + { id = unwrapId categoryId + , position = index } in Cambiatus.Mutation.category @@ -130,16 +129,9 @@ addChild tree (Id newChildId) = { optionals | categories = Tree.children tree - |> List.map (Tree.label >> toSubcategoryInput) - |> (\existingChildren -> - existingChildren - ++ [ { id = newChildId - - -- TODO - Check this - , position = List.length existingChildren - } - ] - ) + |> List.map (Tree.label >> .id) + |> (\childrenIds -> List.take position childrenIds ++ newChildId :: List.drop position childrenIds) + |> List.indexedMap toSubcategoryInput |> OptionalArgument.Present , id = Tree.label tree From ad369ff96a0f80a5707640e6f0095553a942409a Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Thu, 30 Jun 2022 12:36:20 -0300 Subject: [PATCH 096/104] Adjust optimistic updates on click move up/down --- .../Community/Settings/Shop/Categories.elm | 125 +++++++++++++++--- src/elm/Shop/Category.elm | 6 +- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 572fe650e..a4ceddaf6 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -74,6 +74,7 @@ type DropdownState type DropZone = OnTopOf Shop.Category.Id + -- TODO - Add options to select position | OnRoot @@ -137,7 +138,7 @@ type Msg | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) | DraggedOverCategoryForAWhile Shop.Category.Id | CompletedMovingCategory Shop.Category.Id Int (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) - | CompletedMovingCategoryToRoot Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) + | CompletedMovingCategoryToRoot Shop.Category.Id Int (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) | ClickedMoveUp Shop.Category.Id | ClickedMoveDown Shop.Category.Id | GotKeywordDndMsg (Dnd.Msg Int Int) @@ -791,7 +792,7 @@ update msg model loggedIn = CompletedMovingCategory _ _ _ -> UR.init model - CompletedMovingCategoryToRoot categoryId (RemoteData.Success (Just ())) -> + CompletedMovingCategoryToRoot categoryId position (RemoteData.Success (Just ())) -> case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.Success ( _, categories ) -> case findInForest (\{ id } -> id == categoryId) categories of @@ -800,21 +801,17 @@ update msg model loggedIn = Just childZipper -> let - -- TODO - Check this - zipperWithChildOnRoot = - childZipper - |> Tree.Zipper.removeTree - |> Maybe.map - (toFlatForest - >> (\forest -> Tree.Zipper.tree childZipper :: forest) - >> List.sortBy (Tree.label >> .position) - ) - |> Maybe.withDefault (toFlatForest childZipper) + zipperWithMovedChild = + moveToRoot + { childZipper = childZipper + , newPosition = position + } in model |> UR.init |> UR.addExt - (zipperWithChildOnRoot + (zipperWithMovedChild + |> toFlatForest |> Community.ShopCategories |> LoggedIn.SetCommunityField ) @@ -822,7 +819,7 @@ update msg model loggedIn = _ -> UR.init model - CompletedMovingCategoryToRoot categoryId (RemoteData.Success Nothing) -> + CompletedMovingCategoryToRoot categoryId position (RemoteData.Success Nothing) -> UR.init model |> UR.logImpossible msg "Got Nothing when trying to move a child category to root" @@ -831,11 +828,15 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + , extras = + Dict.fromList + [ ( "id", Shop.Category.encodeId categoryId ) + , ( "position", Json.Encode.int position ) + ] } ] - CompletedMovingCategoryToRoot categoryId (RemoteData.Failure err) -> + CompletedMovingCategoryToRoot categoryId position (RemoteData.Failure err) -> UR.init model |> UR.logGraphqlError msg (Just loggedIn.accountName) @@ -844,13 +845,17 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] + , extras = + Dict.fromList + [ ( "id", Shop.Category.encodeId categoryId ) + , ( "position", Json.Encode.int position ) + ] } ] err |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.reorder_error")) - CompletedMovingCategoryToRoot _ _ -> + CompletedMovingCategoryToRoot _ _ _ -> UR.init model ClickedMoveUp categoryId -> @@ -858,14 +863,24 @@ update msg model loggedIn = Just zipper -> case goUpWithoutChildren zipper of Nothing -> + let + newPosition = + case Tree.Zipper.parent zipper of + Just parentZipper -> + (Tree.Zipper.label parentZipper).position - 1 + + Nothing -> + (Tree.Zipper.label zipper).position + in model |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.moveToRoot categoryId + newPosition (Graphql.SelectionSet.succeed ()) ) - (CompletedMovingCategoryToRoot categoryId) + (CompletedMovingCategoryToRoot categoryId newPosition) ) Just newParentZipper -> @@ -907,14 +922,24 @@ update msg model loggedIn = Just zipper -> case goDownWithoutChildren zipper of Nothing -> + let + newPosition = + case Tree.Zipper.parent zipper of + Just parentZipper -> + (Tree.Zipper.label parentZipper).position + 1 + + Nothing -> + (Tree.Zipper.label zipper).position + in model |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.moveToRoot categoryId + newPosition (Graphql.SelectionSet.succeed ()) ) - (CompletedMovingCategoryToRoot categoryId) + (CompletedMovingCategoryToRoot categoryId newPosition) ) Just newParentZipper -> @@ -1043,12 +1068,18 @@ updateDnd loggedIn ext ur = ur OnRoot -> + let + newPosition = + -- TODO + 0 + in UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.moveToRoot draggedElement + newPosition (Graphql.SelectionSet.succeed ()) ) - (CompletedMovingCategoryToRoot draggedElement) + (CompletedMovingCategoryToRoot draggedElement newPosition) ) ur @@ -2363,6 +2394,16 @@ toFlatForest zipper = |> (\( first, others ) -> first :: others) +fromFlatForest : List (Tree.Tree a) -> Maybe (Tree.Zipper.Zipper a) +fromFlatForest trees = + case trees of + first :: others -> + Just (Tree.Zipper.fromForest first others) + + [] -> + Nothing + + goUpWithoutChildren : Tree.Zipper.Zipper a -> Maybe (Tree.Zipper.Zipper a) goUpWithoutChildren zipper = Tree.Zipper.backward zipper @@ -2436,6 +2477,46 @@ moveChild { childZipper, newPosition, parentId } = newParentZipper +moveToRoot : + { childZipper : Tree.Zipper.Zipper Shop.Category.Model + , newPosition : Int + } + -> Tree.Zipper.Zipper Shop.Category.Model +moveToRoot { childZipper, newPosition } = + let + insertNewChild : List Shop.Category.Tree -> List Shop.Category.Tree + insertNewChild forest = + Tree.Zipper.tree childZipper + |> Tree.mapLabel (\category -> { category | position = newPosition }) + |> (\newChild -> newChild :: forest) + + movingId = + Tree.Zipper.label childZipper |> .id + in + childZipper + |> Tree.Zipper.removeTree + |> Maybe.andThen + (\zipperWithoutChild -> + toFlatForest zipperWithoutChild + |> insertNewChild + |> List.map + (\tree -> + let + category = + Tree.label tree + in + if category.position >= newPosition && category.id /= movingId then + Tree.replaceLabel { category | position = category.position + 1 } tree + + else + tree + ) + |> List.sortBy (Tree.label >> .position) + |> fromFlatForest + ) + |> Maybe.withDefault childZipper + + getAllAncestors : Tree.Zipper.Zipper a -> List (Tree.Zipper.Zipper a) getAllAncestors zipper = getAllAncestorsHelper zipper [] @@ -2558,7 +2639,7 @@ msgToString msg = CompletedMovingCategory _ _ r -> [ "CompletedMovingCategory", UR.remoteDataToString r ] - CompletedMovingCategoryToRoot _ r -> + CompletedMovingCategoryToRoot _ _ r -> [ "CompletedMovingCategoryToRoot", UR.remoteDataToString r ] ClickedMoveUp _ -> diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index a8523112c..93da1abe0 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -142,9 +142,9 @@ addChild tree newChildId position = ) -moveToRoot : Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -moveToRoot (Id id) = - -- TODO - Accept position +moveToRoot : Id -> Int -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation +moveToRoot (Id id) position = + -- TODO - Use position Cambiatus.Mutation.category (\optionals -> { optionals From c3cd59818a5b71cec604ba5d9ed62079fb848d2d Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Mon, 4 Jul 2022 23:36:40 -0300 Subject: [PATCH 097/104] Remove position related stuff --- .../Community/Settings/Shop/Categories.elm | 176 +++--------------- src/elm/Shop/Category.elm | 19 +- 2 files changed, 36 insertions(+), 159 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index a4ceddaf6..6c7d220c0 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -28,7 +28,6 @@ import Html.Attributes.Aria exposing (ariaHasPopup, ariaHidden, ariaLabel) import Html.Events exposing (onClick) import Icons import Json.Decode -import Json.Encode import List.Extra import Markdown exposing (Markdown) import Maybe.Extra @@ -137,8 +136,8 @@ type Msg | ClosedMetadataModal | GotDndMsg (Dnd.Msg Shop.Category.Id DropZone) | DraggedOverCategoryForAWhile Shop.Category.Id - | CompletedMovingCategory Shop.Category.Id Int (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) - | CompletedMovingCategoryToRoot Shop.Category.Id Int (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) + | CompletedMovingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe Shop.Category.Id)) (Maybe Shop.Category.Id)) + | CompletedMovingCategoryToRoot Shop.Category.Id (RemoteData (Graphql.Http.Error (Maybe ())) (Maybe ())) | ClickedMoveUp Shop.Category.Id | ClickedMoveDown Shop.Category.Id | GotKeywordDndMsg (Dnd.Msg Int Int) @@ -715,14 +714,13 @@ update msg model loggedIn = Just OnRoot -> UR.init model - CompletedMovingCategory categoryId position (RemoteData.Success (Just parentId)) -> + CompletedMovingCategory categoryId (RemoteData.Success (Just parentId)) -> case getCategoryZipper categoryId of Just childZipper -> let zipperWithMovedChild = moveChild { childZipper = childZipper - , newPosition = position , parentId = parentId } in @@ -753,7 +751,7 @@ update msg model loggedIn = model |> UR.init - CompletedMovingCategory categoryId position (RemoteData.Success Nothing) -> + CompletedMovingCategory categoryId (RemoteData.Success Nothing) -> UR.init model |> UR.logImpossible msg "Got Nothing when trying to move a child category" @@ -762,15 +760,11 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = - Dict.fromList - [ ( "id", Shop.Category.encodeId categoryId ) - , ( "position", Json.Encode.int position ) - ] + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] } ] - CompletedMovingCategory categoryId position (RemoteData.Failure err) -> + CompletedMovingCategory categoryId (RemoteData.Failure err) -> UR.init model |> UR.logGraphqlError msg (Just loggedIn.accountName) @@ -779,20 +773,16 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = - Dict.fromList - [ ( "id", Shop.Category.encodeId categoryId ) - , ( "position", Json.Encode.int position ) - ] + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] } ] err |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.reorder_error")) - CompletedMovingCategory _ _ _ -> + CompletedMovingCategory _ _ -> UR.init model - CompletedMovingCategoryToRoot categoryId position (RemoteData.Success (Just ())) -> + CompletedMovingCategoryToRoot categoryId (RemoteData.Success (Just ())) -> case Community.getField loggedIn.selectedCommunity .shopCategories of RemoteData.Success ( _, categories ) -> case findInForest (\{ id } -> id == categoryId) categories of @@ -802,10 +792,7 @@ update msg model loggedIn = Just childZipper -> let zipperWithMovedChild = - moveToRoot - { childZipper = childZipper - , newPosition = position - } + moveToRoot { childZipper = childZipper } in model |> UR.init @@ -819,7 +806,7 @@ update msg model loggedIn = _ -> UR.init model - CompletedMovingCategoryToRoot categoryId position (RemoteData.Success Nothing) -> + CompletedMovingCategoryToRoot categoryId (RemoteData.Success Nothing) -> UR.init model |> UR.logImpossible msg "Got Nothing when trying to move a child category to root" @@ -828,15 +815,11 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = - Dict.fromList - [ ( "id", Shop.Category.encodeId categoryId ) - , ( "position", Json.Encode.int position ) - ] + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] } ] - CompletedMovingCategoryToRoot categoryId position (RemoteData.Failure err) -> + CompletedMovingCategoryToRoot categoryId (RemoteData.Failure err) -> UR.init model |> UR.logGraphqlError msg (Just loggedIn.accountName) @@ -845,17 +828,13 @@ update msg model loggedIn = , function = "update" } [ { name = "Category" - , extras = - Dict.fromList - [ ( "id", Shop.Category.encodeId categoryId ) - , ( "position", Json.Encode.int position ) - ] + , extras = Dict.fromList [ ( "id", Shop.Category.encodeId categoryId ) ] } ] err |> UR.addExt (LoggedIn.ShowFeedback View.Feedback.Failure (t "shop.categories.reorder_error")) - CompletedMovingCategoryToRoot _ _ _ -> + CompletedMovingCategoryToRoot _ _ -> UR.init model ClickedMoveUp categoryId -> @@ -863,55 +842,26 @@ update msg model loggedIn = Just zipper -> case goUpWithoutChildren zipper of Nothing -> - let - newPosition = - case Tree.Zipper.parent zipper of - Just parentZipper -> - (Tree.Zipper.label parentZipper).position - 1 - - Nothing -> - (Tree.Zipper.label zipper).position - in model |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.moveToRoot categoryId - newPosition (Graphql.SelectionSet.succeed ()) ) - (CompletedMovingCategoryToRoot categoryId newPosition) + (CompletedMovingCategoryToRoot categoryId) ) Just newParentZipper -> - let - newPosition = - if Just newParentZipper == Tree.Zipper.previousSibling zipper then - Tree.Zipper.children newParentZipper - |> List.length - - else - Tree.Zipper.parent zipper - |> Maybe.map - (Tree.Zipper.label - >> .position - >> (\position -> position - 1) - ) - |> Maybe.withDefault - (Tree.Zipper.children newParentZipper - |> List.length - ) - in model |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.addChild (Tree.Zipper.tree newParentZipper) categoryId - newPosition Shop.Category.idSelectionSet ) - (CompletedMovingCategory categoryId newPosition) + (CompletedMovingCategory categoryId) ) Nothing -> @@ -922,47 +872,26 @@ update msg model loggedIn = Just zipper -> case goDownWithoutChildren zipper of Nothing -> - let - newPosition = - case Tree.Zipper.parent zipper of - Just parentZipper -> - (Tree.Zipper.label parentZipper).position + 1 - - Nothing -> - (Tree.Zipper.label zipper).position - in model |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.moveToRoot categoryId - newPosition (Graphql.SelectionSet.succeed ()) ) - (CompletedMovingCategoryToRoot categoryId newPosition) + (CompletedMovingCategoryToRoot categoryId) ) Just newParentZipper -> - let - newPosition = - if Just newParentZipper == Tree.Zipper.nextSibling zipper then - 0 - - else - Tree.Zipper.parent zipper - |> Maybe.map (Tree.Zipper.label >> .position >> (+) 1) - |> Maybe.withDefault 0 - in model |> UR.init |> UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.addChild (Tree.Zipper.tree newParentZipper) categoryId - newPosition Shop.Category.idSelectionSet ) - (CompletedMovingCategory categoryId newPosition) + (CompletedMovingCategory categoryId) ) Nothing -> @@ -1051,35 +980,23 @@ updateDnd loggedIn ext ur = ur Just parentZipper -> - let - newPosition = - Tree.Zipper.children parentZipper - |> List.length - in UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.addChild (Tree.Zipper.tree parentZipper) draggedElement - newPosition Shop.Category.idSelectionSet ) - (CompletedMovingCategory draggedElement newPosition) + (CompletedMovingCategory draggedElement) ) ur OnRoot -> - let - newPosition = - -- TODO - 0 - in UR.addExt (LoggedIn.mutation loggedIn (Shop.Category.moveToRoot draggedElement - newPosition (Graphql.SelectionSet.succeed ()) ) - (CompletedMovingCategoryToRoot draggedElement newPosition) + (CompletedMovingCategoryToRoot draggedElement) ) ur @@ -2430,11 +2347,10 @@ goDownWithoutChildren zipper = moveChild : { childZipper : Tree.Zipper.Zipper Shop.Category.Model - , newPosition : Int , parentId : Shop.Category.Id } -> Tree.Zipper.Zipper Shop.Category.Model -moveChild { childZipper, newPosition, parentId } = +moveChild { childZipper, parentId } = case Tree.Zipper.removeTree childZipper of Nothing -> childZipper @@ -2445,33 +2361,14 @@ moveChild { childZipper, newPosition, parentId } = childZipper Just newParentZipper -> - let - newChild = - Tree.Zipper.label childZipper - - incrementPosition childTree = - let - child = - Tree.label childTree - in - if child.position >= newPosition && child.id /= newChild.id then - Tree.replaceLabel { child | position = child.position + 1 } childTree - - else - childTree - in Tree.Zipper.mapTree (\parentTree -> parentTree - |> Tree.prependChild - (Tree.Zipper.tree childZipper - |> Tree.mapLabel (\c -> { c | position = newPosition }) - ) + |> Tree.prependChild (Tree.Zipper.tree childZipper) |> Tree.mapChildren (\newChildren -> newChildren - |> List.map incrementPosition - |> List.sortBy (Tree.label >> .position) + |> List.sortBy (Tree.label >> .name) ) ) newParentZipper @@ -2479,19 +2376,14 @@ moveChild { childZipper, newPosition, parentId } = moveToRoot : { childZipper : Tree.Zipper.Zipper Shop.Category.Model - , newPosition : Int } -> Tree.Zipper.Zipper Shop.Category.Model -moveToRoot { childZipper, newPosition } = +moveToRoot { childZipper } = let insertNewChild : List Shop.Category.Tree -> List Shop.Category.Tree insertNewChild forest = Tree.Zipper.tree childZipper - |> Tree.mapLabel (\category -> { category | position = newPosition }) |> (\newChild -> newChild :: forest) - - movingId = - Tree.Zipper.label childZipper |> .id in childZipper |> Tree.Zipper.removeTree @@ -2499,19 +2391,7 @@ moveToRoot { childZipper, newPosition } = (\zipperWithoutChild -> toFlatForest zipperWithoutChild |> insertNewChild - |> List.map - (\tree -> - let - category = - Tree.label tree - in - if category.position >= newPosition && category.id /= movingId then - Tree.replaceLabel { category | position = category.position + 1 } tree - - else - tree - ) - |> List.sortBy (Tree.label >> .position) + |> List.sortBy (Tree.label >> .name) |> fromFlatForest ) |> Maybe.withDefault childZipper @@ -2636,10 +2516,10 @@ msgToString msg = DraggedOverCategoryForAWhile _ -> [ "DraggedOverCategoryForAWhile" ] - CompletedMovingCategory _ _ r -> + CompletedMovingCategory _ r -> [ "CompletedMovingCategory", UR.remoteDataToString r ] - CompletedMovingCategoryToRoot _ _ r -> + CompletedMovingCategoryToRoot _ r -> [ "CompletedMovingCategoryToRoot", UR.remoteDataToString r ] ClickedMoveUp _ -> diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 93da1abe0..213fc11d8 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -43,7 +43,6 @@ type alias Model = , metaTitle : Maybe String , metaDescription : Maybe String , metaKeywords : List String - , position : Int } @@ -116,12 +115,12 @@ updateMetadata model { metaTitle, metaDescription, metaKeywords } = ) -addChild : Tree -> Id -> Int -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -addChild tree newChildId position = +addChild : Tree -> Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation +addChild tree newChildId = let toSubcategoryInput index categoryId = { id = unwrapId categoryId - , position = index + , position = 0 } in Cambiatus.Mutation.category @@ -130,7 +129,7 @@ addChild tree newChildId position = | categories = Tree.children tree |> List.map (Tree.label >> .id) - |> (\childrenIds -> List.take position childrenIds ++ newChildId :: List.drop position childrenIds) + |> (\childrenIds -> newChildId :: childrenIds) |> List.indexedMap toSubcategoryInput |> OptionalArgument.Present , id = @@ -142,9 +141,8 @@ addChild tree newChildId position = ) -moveToRoot : Id -> Int -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation -moveToRoot (Id id) position = - -- TODO - Use position +moveToRoot : Id -> SelectionSet decodesTo Cambiatus.Object.Category -> SelectionSet (Maybe decodesTo) RootMutation +moveToRoot (Id id) = Cambiatus.Mutation.category (\optionals -> { optionals @@ -181,7 +179,6 @@ selectionSet = >> Maybe.withDefault [] ) ) - |> SelectionSet.with Cambiatus.Object.Category.position @@ -213,7 +210,7 @@ treesSelectionSet categoriesSelectionSet = Nothing ) children - |> List.sortBy (Tree.label >> .position) + |> List.sortBy (Tree.label >> .name) ) in categoriesSelectionSet selectionSet @@ -228,7 +225,7 @@ treesSelectionSet categoriesSelectionSet = Nothing ) allCategories - |> List.sortBy (Tree.label >> .position) + |> List.sortBy (Tree.label >> .name) ) From 632fc236a7396b2b2a07d0bead9025d6dfdeadd2 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 5 Jul 2022 10:13:21 -0300 Subject: [PATCH 098/104] Send position = 0 --- src/elm/Shop/Category.elm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/elm/Shop/Category.elm b/src/elm/Shop/Category.elm index 213fc11d8..c0a576f07 100644 --- a/src/elm/Shop/Category.elm +++ b/src/elm/Shop/Category.elm @@ -69,6 +69,7 @@ create { icon, name, slug, description, image, parentId } = , name = OptionalArgument.Present name , description = OptionalArgument.Present (Markdown.toRawString description) , imageUri = OptionalArgument.fromMaybe image + , position = OptionalArgument.Present 0 } ) From e1455fb81df96c2da11707a58bfd3b6ce1a2616c Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 5 Jul 2022 10:58:41 -0300 Subject: [PATCH 099/104] Remove menu y margin --- src/elm/Page/Community/Settings/Shop/Categories.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 6c7d220c0..719d87172 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1552,7 +1552,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model DropdownOpenOnButton actionsDropdown -> actionsDropdown == category.id - DropdownOpenOnMouse _ actionsDropdown -> + DropdownOpenOnMouse _ _ -> False hasActionsMenuOpen = @@ -1633,7 +1633,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model [] in menu - (class "bg-white border border-gray-300 rounded-md p-2 text-sm shadow-lg animate-fade-in-from-above-sm marker-hidden" + (class "bg-white border border-gray-300 rounded-md p-2 my-0 text-sm shadow-lg animate-fade-in-from-above-sm marker-hidden" :: classList [ ( "absolute right-0 top-full", model.actionsDropdown == DropdownOpenOnButton category.id ) ] From 0905fd6f0d7ee13adb97269a914083122fb10571 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 5 Jul 2022 11:01:55 -0300 Subject: [PATCH 100/104] Add more spacing on modal --- src/elm/Page/Community/Settings/Shop/Categories.elm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 719d87172..ec15506e4 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1751,11 +1751,12 @@ viewCategoryMetadataModal translators community category formModel = { isVisible = True , closeMsg = ClosedMetadataModal } - |> Modal.withHeader "Editing category sharing data" + -- TODO - I18N + |> Modal.withHeaderElement (p [ class "md:px-2 md:mt-2" ] [ text "Editing category sharing data" ]) |> Modal.withBody - [ p [ class "mb-6" ] + [ p [ class "mb-6 md:px-2" ] [ text <| translators.t "shop.categories.metadata.guidance" ] - , Form.viewWithoutSubmit [ class "mt-2" ] + , Form.viewWithoutSubmit [ class "mt-2 md:px-2" ] translators (\_ -> []) (metadataForm translators community category) From 6782784a296104609ee4f2c14706e89feb1a934d Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Tue, 5 Jul 2022 11:04:41 -0300 Subject: [PATCH 101/104] Translate modal title --- public/translations/amh-ETH.json | 1 + public/translations/cat-CAT.json | 1 + public/translations/en-US.json | 1 + public/translations/es-ES.json | 1 + public/translations/pt-BR.json | 1 + src/elm/Page/Community/Settings/Shop/Categories.elm | 3 +-- 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/translations/amh-ETH.json b/public/translations/amh-ETH.json index 927baf3d0..ab07d0fcb 100644 --- a/public/translations/amh-ETH.json +++ b/public/translations/amh-ETH.json @@ -899,6 +899,7 @@ "image_guidance": "ምስሎችን በከፍተኛ ጥራት በወርድ ሁነታ ይምረጡ" }, "metadata": { + "title": "ውሂብ ማጋራት", "guidance": "ይህ መረጃ ይህን ምድብ ሲያጋሩ ተጨማሪ መረጃን ለማሳየት ይጠቅማል።", "preview": "ቅድመ እይታ", "preview_text": "ይህ የተጋራው ይዘት ምን እንደሚመስል ግምታዊ ግምት ነው። አገናኙ እየተጋራበት ባለው መድረክ ላይ በመመስረት ሊለወጥ ይችላል።" diff --git a/public/translations/cat-CAT.json b/public/translations/cat-CAT.json index 2387deaa3..611e81223 100755 --- a/public/translations/cat-CAT.json +++ b/public/translations/cat-CAT.json @@ -941,6 +941,7 @@ "image_guidance": "Preferiu imatges en mode horitzontal amb alta qualitat" }, "metadata": { + "title": "Contingut compartit", "guidance": "Aquesta informació s'utilitzarà per mostrar més informació en compartir aquesta categoria.", "preview": "Vista prèvia", "preview_text": "Aquesta és una aproximació de com serà el contingut compartit. Pot canviar en funció de la plataforma en què es comparteix l'enllaç." diff --git a/public/translations/en-US.json b/public/translations/en-US.json index 18f2a3a64..f99355532 100755 --- a/public/translations/en-US.json +++ b/public/translations/en-US.json @@ -938,6 +938,7 @@ "image_guidance": "Prefer images in landscape mode with high quality" }, "metadata": { + "title": "Sharing data", "guidance": "This information will be used to display rich links when sharing this category.", "preview": "Preview", "preview_text": "This is an approximation of what the shared content will look like. It might change depending on the platform the link is being shared on." diff --git a/public/translations/es-ES.json b/public/translations/es-ES.json index 9864c3188..05bec4714 100755 --- a/public/translations/es-ES.json +++ b/public/translations/es-ES.json @@ -941,6 +941,7 @@ "image_guidance": "Prefiere imágenes en modo horizontal con alta calidad" }, "metadata": { + "title": "Contenido compartido", "guidance": "Esta información se utilizará para mostrar más información al compartir esta categoría.", "preview": "Avance", "preview_text": "Esta es una aproximación de cómo se verá el contenido compartido. Puede cambiar según la plataforma en la que se comparte el enlace." diff --git a/public/translations/pt-BR.json b/public/translations/pt-BR.json index ab7985dd3..3bafd781e 100755 --- a/public/translations/pt-BR.json +++ b/public/translations/pt-BR.json @@ -945,6 +945,7 @@ "image_guidance": "Prefira imagens em modo paisagem com alta qualidade" }, "metadata": { + "title": "Dados de compartilhamento", "guidance": "Essa informação vai ser usada para mostrar mais informações quando compartilhando esta categoria", "preview": "Pré-visualização", "preview_text": "Essa é uma aproximação de como o conteúdo compartilhado será exibido. Isso pode mudar dependendo da plataforma em que o link está sendo compartilhado." diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index ec15506e4..6a294e9f7 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1751,8 +1751,7 @@ viewCategoryMetadataModal translators community category formModel = { isVisible = True , closeMsg = ClosedMetadataModal } - -- TODO - I18N - |> Modal.withHeaderElement (p [ class "md:px-2 md:mt-2" ] [ text "Editing category sharing data" ]) + |> Modal.withHeaderElement (p [ class "md:px-2 md:mt-2" ] [ text <| translators.t "shop.categories.metadata.title" ]) |> Modal.withBody [ p [ class "mb-6 md:px-2" ] [ text <| translators.t "shop.categories.metadata.guidance" ] From 68a7fb31b3c86e3a7a68326a2e7dc175682b9f66 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 6 Jul 2022 15:58:21 -0300 Subject: [PATCH 102/104] Only root categories are bold --- src/elm/Page/Community/Settings/Shop/Categories.elm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 6a294e9f7..46b401ad4 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1419,7 +1419,10 @@ viewCategoryWithChildren translators model zipper children = Just icon -> img [ src icon, alt "", class "h-6 w-6 rounded-full mr-2" ] [] - , span [ class "whitespace-nowrap font-bold text-black" ] + , span + [ class "whitespace-nowrap text-black" + , classList [ ( "font-bold", Maybe.Extra.isNothing category.parentId ) ] + ] [ text category.name ] ] From ebaa3c42113c1be1ae9270cefed3cb9c5265db62 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 6 Jul 2022 16:10:44 -0300 Subject: [PATCH 103/104] Prevent context menu on mobile --- .../Community/Settings/Shop/Categories.elm | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 46b401ad4..7ec787530 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -1395,19 +1395,24 @@ viewCategoryWithChildren translators model zipper children = ] :: onClick (ClickedToggleExpandCategory category.id) :: Html.Events.preventDefaultOn "contextmenu" - (Json.Decode.map2 - (\x y -> - ( OpenedContextMenuForAction { x = x, y = y } category.id - , case model.actionsDropdown of - DropdownOpenOnMouse _ _ -> - False - - _ -> - True - ) + (Json.Decode.map3 + (\x y button -> + if button == 0 then + ( NoOp, False ) + + else + ( OpenedContextMenuForAction { x = x, y = y } category.id + , case model.actionsDropdown of + DropdownOpenOnMouse _ _ -> + False + + _ -> + True + ) ) (Json.Decode.field "clientX" Json.Decode.float) (Json.Decode.field "clientY" Json.Decode.float) + (Json.Decode.field "button" Json.Decode.int) ) :: Dnd.draggable category.id GotDndMsg ) From d4e55f80e7c8127707526461045c563e8f05b207 Mon Sep 17 00:00:00 2001 From: Henrique Buss Date: Wed, 6 Jul 2022 16:37:52 -0300 Subject: [PATCH 104/104] Show context menu on different side depending on click position --- .../Community/Settings/Shop/Categories.elm | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/elm/Page/Community/Settings/Shop/Categories.elm b/src/elm/Page/Community/Settings/Shop/Categories.elm index 7ec787530..a87ebe952 100644 --- a/src/elm/Page/Community/Settings/Shop/Categories.elm +++ b/src/elm/Page/Community/Settings/Shop/Categories.elm @@ -67,7 +67,7 @@ type alias Model = type DropdownState = DropdownClosed - | DropdownOpenOnMouse { x : Float, y : Float } Shop.Category.Id + | DropdownOpenOnMouse { x : Float, y : Float } Shop.Category.Id Browser.Dom.Viewport | DropdownOpenOnButton Shop.Category.Id @@ -129,6 +129,7 @@ type Msg | CompletedDeletingCategory Shop.Category.Id (RemoteData (Graphql.Http.Error Api.Graphql.DeleteStatus.DeleteStatus) Api.Graphql.DeleteStatus.DeleteStatus) | ClickedShowActionsDropdown Shop.Category.Id | OpenedContextMenuForAction { x : Float, y : Float } Shop.Category.Id + | GotViewportForContextMenuForAction { x : Float, y : Float } Shop.Category.Id Browser.Dom.Viewport | ClosedActionsDropdown | ClickedOpenMetadataModal Shop.Category.Id | GotMetadataFormMsg (Form.Msg MetadataFormInput) @@ -583,22 +584,32 @@ update msg model loggedIn = DropdownOpenOnButton _ -> DropdownClosed - DropdownOpenOnMouse _ _ -> + DropdownOpenOnMouse _ _ _ -> DropdownOpenOnButton categoryId } |> UR.init OpenedContextMenuForAction coordinates categoryId -> + let + getViewport = + Browser.Dom.getViewport + |> Task.perform (\viewport -> GotViewportForContextMenuForAction coordinates categoryId viewport) + in + model + |> UR.init + |> UR.addCmd getViewport + + GotViewportForContextMenuForAction coordinates categoryId viewport -> { model | actionsDropdown = case model.actionsDropdown of DropdownClosed -> - DropdownOpenOnMouse coordinates categoryId + DropdownOpenOnMouse coordinates categoryId viewport DropdownOpenOnButton _ -> - DropdownOpenOnMouse coordinates categoryId + DropdownOpenOnMouse coordinates categoryId viewport - DropdownOpenOnMouse _ _ -> + DropdownOpenOnMouse _ _ _ -> DropdownClosed } |> UR.init @@ -1078,7 +1089,7 @@ viewPageContainer translators { children, modals } model = DropdownOpenOnButton _ -> True - DropdownOpenOnMouse _ _ -> + DropdownOpenOnMouse _ _ _ -> True in div [ class "container mx-auto sm:px-4 sm:mt-6 pb-40 overflow-x-hidden" ] @@ -1314,7 +1325,7 @@ viewCategoryWithChildren translators model zipper children = actionsDropdown (Tree.Zipper.tree zipper) - DropdownOpenOnMouse _ actionsDropdown -> + DropdownOpenOnMouse _ actionsDropdown _ -> isAncestorOf actionsDropdown (Tree.Zipper.tree zipper) @@ -1403,7 +1414,7 @@ viewCategoryWithChildren translators model zipper children = else ( OpenedContextMenuForAction { x = x, y = y } category.id , case model.actionsDropdown of - DropdownOpenOnMouse _ _ -> + DropdownOpenOnMouse _ _ _ -> False _ -> @@ -1443,7 +1454,7 @@ viewCategoryWithChildren translators model zipper children = DropdownOpenOnButton actionsDropdown -> actionsDropdown == category.id - DropdownOpenOnMouse _ actionsDropdown -> + DropdownOpenOnMouse _ actionsDropdown _ -> actionsDropdown == category.id ) ] @@ -1549,7 +1560,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model DropdownOpenOnButton actionsDropdown -> actionsDropdown == category.id - DropdownOpenOnMouse _ actionsDropdown -> + DropdownOpenOnMouse _ actionsDropdown _ -> actionsDropdown == category.id isDropdownOpenOnButton = @@ -1560,7 +1571,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model DropdownOpenOnButton actionsDropdown -> actionsDropdown == category.id - DropdownOpenOnMouse _ _ -> + DropdownOpenOnMouse _ _ _ -> False hasActionsMenuOpen = @@ -1573,7 +1584,7 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model actionsDropdown (Tree.Zipper.tree zipper) - DropdownOpenOnMouse _ actionsDropdown -> + DropdownOpenOnMouse _ actionsDropdown _ -> isAncestorOf actionsDropdown (Tree.Zipper.tree zipper) @@ -1631,10 +1642,14 @@ viewActions translators { isParentOfNewCategoryForm, isDraggingSomething } model let openOnMouseAttrs = case model.actionsDropdown of - DropdownOpenOnMouse { x, y } _ -> - [ style "left" (String.fromFloat x ++ "px") + DropdownOpenOnMouse { x, y } _ { viewport } -> + [ class "fixed" , style "top" (String.fromFloat y ++ "px") - , class "fixed" + , if x < viewport.width / 2 then + style "left" (String.fromFloat x ++ "px") + + else + style "right" (String.fromFloat (viewport.width - x) ++ "px") ] _ -> @@ -2285,7 +2300,7 @@ subscriptions model = DropdownOpenOnButton _ -> closeDropdownSubscription - DropdownOpenOnMouse _ _ -> + DropdownOpenOnMouse _ _ _ -> closeDropdownSubscription ] @@ -2503,6 +2518,9 @@ msgToString msg = OpenedContextMenuForAction _ _ -> [ "OpenedContextMenuForAction" ] + GotViewportForContextMenuForAction _ _ _ -> + [ "GotViewportForContextMenuForAction" ] + ClosedActionsDropdown -> [ "ClosedActionsDropdown" ]