diff --git a/.env.sample b/.env.sample index 8a983ce25..8335c5d8d 100644 --- a/.env.sample +++ b/.env.sample @@ -8,7 +8,8 @@ ENCRYPTION_KEY=please-change-this-with-32-chars EMAIL_HOST_PASSWORD=please-change-this EMAIL_HOST_USER=please-change-this ENABLE_FOOD_SECTION=True -ENABLE_OBJECT_SECTION=True +ENABLE_OBJECTS_SECTION=True +ENABLE_VELI_SECTION=True MATOMO_HOST=stats.beta.gouv.fr MATOMO_SITE_ID=57 MATOMO_TOKEN=xxx diff --git a/README.md b/README.md index 41589fef4..c06be5b65 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,8 @@ Les variables d'environnement suivantes doivent être définies : - `EMAIL_HOST_USER`: l'utilisateur du compte SMTP - `EMAIL_HOST_PASSWORD` : le mot de passe du compte SMTP pour envoyer les mail liés à l'authentification - `ENABLE_FOOD_SECTION` : affichage ou non de la section expérimentale dédiée à l'alimentaire (valeur `True` ou `False`, par défault `False`) -- `ENABLE_OBJECT_SECTION` : affichage ou non de la section expérimentale dédiée aux objets génériques (valeur `True` ou `False`, par défault `False`) +- `ENABLE_OBJECTS_SECTION` : affichage ou non de la section expérimentale dédiée aux objets génériques (valeur `True` ou `False`, par défault `False`) +- `ENABLE_VELI_SECTION` : affichage ou non de la section expérimentale dédiée aux véhicules intermédiaires (valeur `True` ou `False`, par défault `False`) - `MATOMO_HOST`: le domaine de l'instance Matomo permettant le suivi d'audience du produit (typiquement `stats.beta.gouv.fr`). - `MATOMO_SITE_ID`: l'identifiant du site Ecobalyse sur l'instance Matomo permettant le suivi d'audience du produit. - `MATOMO_TOKEN`: le token Matomo permettant le suivi d'audience du produit. diff --git a/index.js b/index.js index 317e74650..1a5096208 100644 --- a/index.js +++ b/index.js @@ -37,8 +37,12 @@ const storeKey = "store"; const app = Elm.Main.init({ flags: { clientUrl: location.origin + location.pathname, - enableFoodSection: process.env.ENABLE_FOOD_SECTION === "True", - enableObjectSection: process.env.ENABLE_OBJECT_SECTION === "True", + enabledSections: { + food: process.env.ENABLE_FOOD_SECTION === "True", + objects: process.env.ENABLE_OBJECTS_SECTION === "True", + textile: true, // always enabled + veli: process.env.ENABLE_VELI_SECTION === "True", + }, rawStore: localStorage[storeKey] || "null", matomo: { host: process.env.MATOMO_HOST || "", diff --git a/public/data/food/examples.json b/public/data/food/examples.json index bb34ef6a1..a0686ae1c 100644 --- a/public/data/food/examples.json +++ b/public/data/food/examples.json @@ -2,6 +2,7 @@ { "id": "1d04242a-6312-4d92-9233-8b485d128e65", "name": "Produit vide", + "scope": "food", "category": "", "query": { "ingredients": [] @@ -10,6 +11,7 @@ { "id": "b2b9f235-e7c4-4727-aa7a-1f7c8b5a9812", "name": "Farine de blé bio FR (1kg) - 20", + "scope": "food", "category": "Produits céréaliers", "query": { "ingredients": [ @@ -31,6 +33,7 @@ { "id": "3c7d4110-6064-45a2-a7de-6649fa72ca1a", "name": "Farine de blé FR (1kg) - 22", + "scope": "food", "category": "Produits céréaliers", "query": { "ingredients": [ @@ -52,6 +55,7 @@ { "id": "e2de4127-69e0-4436-b814-efb648ac9ff7", "name": "Farine de blé origine Ukraine (1kg) - 22", + "scope": "food", "category": "Produits céréaliers", "query": { "ingredients": [ @@ -72,6 +76,7 @@ { "id": "55539367-a21b-4da1-8009-e2620c18404f", "name": "Filets de poulet FR (250g) - 96", + "scope": "food", "category": "Volaille", "query": { "ingredients": [ @@ -94,6 +99,7 @@ { "id": "af215046-d6af-470c-975f-643ad0be35e5", "name": "Filets de poulet origine Brésil (250g) - 100", + "scope": "food", "category": "Volaille", "query": { "ingredients": [ @@ -116,6 +122,7 @@ { "id": "b9966def-463f-4ad0-b225-5da94f80de73", "name": "Pizza bolognese (375g) - 21", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -181,6 +188,7 @@ { "id": "6fb777d4-eae2-4594-9f0c-7fc4e2853b97", "name": "Pizza bolognese FR (375g) - 21", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -257,6 +265,7 @@ { "id": "786839bf-f348-4609-b2d4-75d3f35c692d", "name": "Pizza bolognese FR bio (375g) - 21", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -333,6 +342,7 @@ { "id": "4ce49252-07c4-4e2a-a451-dc26f1550889", "name": "Pizza royale (350g) - 6", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -390,6 +400,7 @@ { "id": "d97ee8a2-1e8b-425c-9ea4-c1d67d2640f9", "name": "Pizza royale FR (350g) - 6", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -455,6 +466,7 @@ { "id": "1a2911dd-d32b-4e4e-9c31-a5eceaea66bb", "name": "Pizza végétale (385g) - 19", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -520,6 +532,7 @@ { "id": "dafff308-db3f-4c3d-aa9c-b626c1b4ae17", "name": "Pizza végétale FR (385g) - 19", + "scope": "food", "category": "Produit surgelé", "query": { "ingredients": [ @@ -595,6 +608,7 @@ { "id": "b375f93b-b8c2-4849-ab32-8a731a8212ca", "name": "Steak haché surgelé origine Brésil (200g) - 82", + "scope": "food", "category": "Viande bovine", "query": { "ingredients": [ @@ -617,6 +631,7 @@ { "id": "f5872248-3fa2-49cd-928e-df1adb9c7094", "name": "Steak haché surgelé FR (250g) - 95", + "scope": "food", "category": "Viande bovine", "query": { "ingredients": [ @@ -639,6 +654,7 @@ { "id": "2fd25fd5-48fa-49b5-b5f2-e9b1fc5db166", "name": "Steak haché surgelé bio FR (200g) - 78", + "scope": "food", "category": "Viande bovine", "query": { "ingredients": [ diff --git a/public/data/object/examples.json b/public/data/object/examples.json index c00469f70..9bbf5246f 100644 --- a/public/data/object/examples.json +++ b/public/data/object/examples.json @@ -1,7 +1,17 @@ [ { "id": "0d08a9ff-5683-4d3d-a7b8-dce74f37cd3b", - "name": "Produit vide", + "name": "Objet par défaut", + "scope": "object", + "category": "", + "query": { + "processes": [] + } + }, + { + "id": "cefc2585-7125-4610-aadc-69970a3d67d1", + "name": "Véhicule intermédiaire par défaut", + "scope": "veli", "category": "", "query": { "processes": [] @@ -10,6 +20,7 @@ { "id": "7d78d30e-7c35-451f-b8ab-e590f39ed0e8", "name": "Chaise", + "scope": "object", "category": "", "query": { "processes": [ @@ -27,6 +38,7 @@ { "id": "4d14194a-8c62-4c23-8799-745c498d1f6b", "name": "Table", + "scope": "object", "category": "", "query": { "processes": [ @@ -36,5 +48,19 @@ } ] } + }, + { + "id": "b5aeadf6-95f2-4ee2-b528-899b54f62a20", + "name": "VAE", + "scope": "veli", + "category": "", + "query": { + "processes": [ + { + "process_id": "3295b2a5-328a-4c00-b046-e2ddeb0da823", + "amount": 35 + } + ] + } } ] diff --git a/public/data/textile/examples.json b/public/data/textile/examples.json index 29b79cfb1..00c0a5251 100644 --- a/public/data/textile/examples.json +++ b/public/data/textile/examples.json @@ -2,6 +2,7 @@ { "id": "c0500a78-8b40-4e92-88df-c2a39e1a08c3", "name": "Tshirt lin (150g) - France - Mode \"éthique\"", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -26,6 +27,7 @@ { "id": "b9dc9be5-ba32-4b8c-9190-b93098cf1d0d", "name": "Tshirt coton bio (150g) - France - Mode \"éthique\"", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -50,6 +52,7 @@ { "id": "dffc6a19-794a-4d14-ad1a-bc3512359f22", "name": "Pull polyester (550g) - Asie - Mode \"ultra fast fashion\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -74,6 +77,7 @@ { "id": "f02b1029-0d04-4239-94d1-b384ab3316cb", "name": "Pull coton (550g) - Chine - Mode \"fast fashion\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -98,6 +102,7 @@ { "id": "c605e8cc-7f3e-42b6-afc1-9358bda9a633", "name": "Pull viscose (550g) - Chine - Mode \"fast fashion\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -126,6 +131,7 @@ { "id": "9986e023-47f7-42c7-8c1b-4fe1371f2f59", "name": "Pull coton (550g) - Pakistan - Mode \"traditionnelle\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -150,6 +156,7 @@ { "id": "dc4a1762-ac99-4d41-8ef6-1f4d647dec62", "name": "Pull coton bio (550g) - France - Mode \"traditionnelle\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -174,6 +181,7 @@ { "id": "8a57688b-1da6-4fc4-85aa-a27c6cdf4a56", "name": "Pull laine (550g) - France - Mode \"éthique\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -198,6 +206,7 @@ { "id": "611fd763-8df3-40d1-a51a-e8073bb3aaf0", "name": "Pull laine paysane (550g) - France - Mode \"éthique\"", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -223,6 +232,7 @@ { "id": "64ec2a97-5b87-4dc8-a9c2-96f3b50e9fe2", "name": "Tshirt synthétique (150g) - Asie - Mode \"ultra fast fashion\"", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -251,6 +261,7 @@ { "id": "351d73dc-9c50-4aa8-858a-2bd5448d1f26", "name": "Tshirt coton (150g) - Chine - Mode \"fast fashion\"", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -279,6 +290,7 @@ { "id": "16c16705-3169-41bf-b1b2-6382b00c2118", "name": "Tshirt coton (150g) - Pakistan - Mode \"traditionnelle\"", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -303,6 +315,7 @@ { "id": "5dbb2cfb-dc3a-48cc-bc7b-aaefbc3cc285", "name": "Tshirt coton (150g) - France - Mode \"traditionnelle\"", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -327,6 +340,7 @@ { "id": "307b3586-41f4-4d93-833a-e03b5be137cc", "name": "Jupe coton (300g) - Majorant par défaut", + "scope": "textile", "category": "Jupe / Robe", "query": { "mass": 0.3, @@ -345,6 +359,7 @@ { "id": "536e3785-039a-49d6-a119-3cfce485420c", "name": "Chemise coton (250g) - Majorant par défaut", + "scope": "textile", "category": "Chemise", "query": { "mass": 0.25, @@ -363,6 +378,7 @@ { "id": "bf24fd40-9a2a-4489-935b-d82c339e5abe", "name": "Jean coton (450g) - Majorant par défaut", + "scope": "textile", "category": "Jean", "query": { "mass": 0.45, @@ -382,6 +398,7 @@ { "id": "f86835d3-7018-40fa-8144-2d1f6aa264b9", "name": "Pantalon coton (450g) - Majorant par défaut", + "scope": "textile", "category": "Pantalon", "query": { "mass": 0.45, @@ -400,6 +417,7 @@ { "id": "32c8973b-864f-4060-977d-a0738f6414c4", "name": "Manteau coton (950g) - Majorant par défaut", + "scope": "textile", "category": "Manteau / Veste", "query": { "mass": 0.95, @@ -418,6 +436,7 @@ { "id": "f48d7a6b-68db-4d1c-9623-6ec0ab89cbdb", "name": "Tshirt coton (150g) - Majorant par défaut", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -438,6 +457,7 @@ { "id": "6a2da9ff-9120-4a47-91ad-a8e72be45f4a", "name": "Tshirt coton (150g) - Remanufacturé", + "scope": "textile", "category": "Tshirt / Polo", "query": { "mass": 0.15, @@ -463,6 +483,7 @@ { "id": "190ccaed-6883-4dd9-a70b-918878ccced6", "name": "Pull coton (550g) - Majorant par défaut", + "scope": "textile", "category": "Pull", "query": { "mass": 0.55, @@ -483,6 +504,7 @@ { "id": "47f8d396-9f57-4a1d-8d24-b2fc3439e75f", "name": "Chaussettes coton (40g) - Majorant par défaut", + "scope": "textile", "category": "Chaussettes", "query": { "mass": 0.04, @@ -503,6 +525,7 @@ { "id": "3f3be8b2-115b-41b4-b925-5656aa7d8c87", "name": "Caleçon coton (40g) - Majorant par défaut", + "scope": "textile", "category": "Caleçon (tissé)", "query": { "mass": 0.04, @@ -523,6 +546,7 @@ { "id": "69a21510-b23b-4910-9bd9-23507c8f5a59", "name": "Slip coton (30g) - Majorant par défaut", + "scope": "textile", "category": "Boxer / slip (tricoté)", "query": { "mass": 0.03, @@ -543,6 +567,7 @@ { "id": "9d382f50-f77a-44cd-b26f-702c4a2ceb50", "name": "Maillot de bain polyester (100g) - Majorant par défaut", + "scope": "textile", "category": "Maillot de bain", "query": { "mass": 0.1, diff --git a/src/Data/Bookmark.elm b/src/Data/Bookmark.elm index 842ae2f38..6eae8da41 100644 --- a/src/Data/Bookmark.elm +++ b/src/Data/Bookmark.elm @@ -9,6 +9,7 @@ module Data.Bookmark exposing , isFood , isObject , isTextile + , isVeli , sort , toId , toQueryDescription @@ -21,6 +22,7 @@ import Data.Scope as Scope exposing (Scope) import Data.Textile.Inputs as Inputs import Data.Textile.Query as TextileQuery import Json.Decode as Decode exposing (Decoder) +import Json.Decode.Pipeline as JDP import Json.Encode as Encode import Static.Db exposing (Db) import Time exposing (Posix) @@ -30,6 +32,7 @@ type alias Bookmark = { created : Posix , name : String , query : Query + , subScope : Maybe Scope } @@ -37,14 +40,25 @@ type Query = Food FoodQuery.Query | Object ObjectQuery.Query | Textile TextileQuery.Query + | Veli ObjectQuery.Query decode : Decoder Bookmark decode = - Decode.map3 Bookmark - (Decode.field "created" (Decode.map Time.millisToPosix Decode.int)) - (Decode.field "name" Decode.string) - (Decode.field "query" decodeQuery) + Decode.succeed Bookmark + |> JDP.required "created" (Decode.map Time.millisToPosix Decode.int) + |> JDP.required "name" Decode.string + |> JDP.required "query" decodeQuery + |> JDP.optional "subScope" (Decode.maybe Scope.decode) Nothing + |> Decode.map + (\bookmark -> + case ( bookmark.query, bookmark.subScope ) of + ( Object q, Just Scope.Veli ) -> + { bookmark | query = Veli q } + + _ -> + bookmark + ) decodeQuery : Decoder Query @@ -62,6 +76,17 @@ encode v = [ ( "created", Encode.int <| Time.posixToMillis v.created ) , ( "name", Encode.string v.name ) , ( "query", encodeQuery v.query ) + , ( "subScope" + , case v.subScope of + Just Scope.Object -> + Scope.encode Scope.Object + + Just Scope.Veli -> + Scope.encode Scope.Veli + + _ -> + Encode.null + ) ] @@ -77,6 +102,9 @@ encodeQuery v = Textile query -> TextileQuery.encode query + Veli query -> + ObjectQuery.encode query + isFood : Bookmark -> Bool isFood { query } = @@ -108,6 +136,16 @@ isTextile { query } = False +isVeli : Bookmark -> Bool +isVeli { query } = + case query of + Veli _ -> + True + + _ -> + False + + findByQuery : Query -> List Bookmark -> Maybe Bookmark findByQuery query = List.filter (.query >> (==) query) @@ -141,6 +179,9 @@ scope bookmark = Textile _ -> Scope.Textile + Veli _ -> + Scope.Veli + sort : List Bookmark -> List Bookmark sort = @@ -171,3 +212,8 @@ toQueryDescription db bookmark = |> Inputs.fromQuery db |> Result.map Inputs.toString |> Result.withDefault bookmark.name + + Veli objectQuery -> + objectQuery + |> ObjectQuery.toString db.object.processes + |> Result.withDefault "N/A" diff --git a/src/Data/Dataset.elm b/src/Data/Dataset.elm index 969f4861c..6f13833db 100644 --- a/src/Data/Dataset.elm +++ b/src/Data/Dataset.elm @@ -68,6 +68,10 @@ datasets scope = , TextileProducts Nothing ] + Scope.Veli -> + [ Impacts Nothing + ] + fromSlug : String -> Dataset fromSlug string = diff --git a/src/Data/Example.elm b/src/Data/Example.elm index adaaeafc7..54455b20d 100644 --- a/src/Data/Example.elm +++ b/src/Data/Example.elm @@ -4,11 +4,13 @@ module Data.Example exposing , findByName , findByQuery , findByUuid + , forScope , parseUuid , toCategory , toName ) +import Data.Scope as Scope exposing (Scope) import Data.Uuid as Uuid exposing (Uuid) import Json.Decode as Decode exposing (Decoder) import Url.Parser as Parser exposing (Parser) @@ -19,16 +21,18 @@ type alias Example query = , id : Uuid , name : String , query : query + , scope : Scope } decode : Decoder query -> Decoder (Example query) decode decodeQuery = - Decode.map4 Example + Decode.map5 Example (Decode.field "category" Decode.string) (Decode.field "id" Uuid.decoder) (Decode.field "name" Decode.string) (Decode.field "query" decodeQuery) + (Decode.field "scope" Scope.decode) decodeListFromJsonString : Decoder query -> String -> Result String (List (Example query)) @@ -58,6 +62,11 @@ findByQuery query = >> Result.fromMaybe "Exemple introuvable" +forScope : Scope -> List (Example query) -> List (Example query) +forScope scope = + List.filter (.scope >> (==) scope) + + parseUuid : Parser (Uuid -> a) a parseUuid = Parser.custom "EXAMPLE" Uuid.fromString diff --git a/src/Data/Object/Process.elm b/src/Data/Object/Process.elm index 3938ebf0c..bbd8d18b6 100644 --- a/src/Data/Object/Process.elm +++ b/src/Data/Object/Process.elm @@ -61,7 +61,7 @@ encode process = Encode.object [ ( "comment", Encode.string process.comment ) , ( "density", Encode.float process.density ) - , ( "displayName", Encode.string process.displayName ) + , ( "display_name", Encode.string process.displayName ) , ( "id", encodeId process.id ) , ( "impacts", Impact.encode process.impacts ) , ( "name", Encode.string process.name ) diff --git a/src/Data/Object/Query.elm b/src/Data/Object/Query.elm index 09bc37702..affa6f507 100644 --- a/src/Data/Object/Query.elm +++ b/src/Data/Object/Query.elm @@ -18,6 +18,7 @@ module Data.Object.Query exposing import Base64 import Data.Object.Process as Process exposing (Process) +import Data.Scope as Scope exposing (Scope) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode import Result.Extra as RE @@ -49,14 +50,14 @@ amountToFloat (Amount float) = float -buildApiQuery : String -> Query -> String -buildApiQuery clientUrl query = +buildApiQuery : Scope -> String -> Query -> String +buildApiQuery scope clientUrl query = """curl -sS -X POST %apiUrl% \\ -H "accept: application/json" \\ -H "content-type: application/json" \\ -d '%json%' """ - |> String.replace "%apiUrl%" (clientUrl ++ "api/object/simulator") + |> String.replace "%apiUrl%" (clientUrl ++ "api/" ++ Scope.toString scope ++ "/simulator") |> String.replace "%json%" (encode query |> Encode.encode 0) diff --git a/src/Data/Scope.elm b/src/Data/Scope.elm index 63680367d..d1d7a4dd4 100644 --- a/src/Data/Scope.elm +++ b/src/Data/Scope.elm @@ -18,6 +18,7 @@ type Scope = Food | Object | Textile + | Veli decode : Decoder Scope @@ -43,6 +44,9 @@ fromString string = "textile" -> Ok Textile + "veli" -> + Ok Veli + _ -> Err <| "Couldn't decode unknown scope " ++ string @@ -70,6 +74,9 @@ toLabel scope = Textile -> "Textile" + Veli -> + "Véhicules intermédiaires" + toString : Scope -> String toString scope = @@ -82,3 +89,6 @@ toString scope = Textile -> "textile" + + Veli -> + "veli" diff --git a/src/Data/Session.elm b/src/Data/Session.elm index c3f6129ae..123aebc41 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -1,5 +1,6 @@ module Data.Session exposing ( Auth(..) + , EnabledSections , Notification(..) , Session , Store @@ -13,6 +14,7 @@ module Data.Session exposing , logout , notifyError , notifyInfo + , objectQueryFromScope , saveBookmark , selectAllBookmarks , selectNoBookmarks @@ -28,6 +30,7 @@ import Data.Bookmark as Bookmark exposing (Bookmark) import Data.Food.Query as FoodQuery import Data.Github as Github import Data.Object.Query as ObjectQuery +import Data.Scope as Scope exposing (Scope) import Data.Textile.Query as TextileQuery import Data.User as User exposing (User) import Json.Decode as Decode exposing (Decoder) @@ -44,6 +47,7 @@ type alias Queries = { food : FoodQuery.Query , object : ObjectQuery.Query , textile : TextileQuery.Query + , veli : ObjectQuery.Query } @@ -51,8 +55,7 @@ type alias Session = { clientUrl : String , currentVersion : Version , db : Db - , enableFoodSection : Bool - , enableObjectSection : Bool + , enabledSections : EnabledSections , matomo : { host : String, siteId : String } , navKey : Nav.Key , notifications : List Notification @@ -62,6 +65,14 @@ type alias Session = } +type alias EnabledSections = + { food : Bool + , objects : Bool + , textile : Bool + , veli : Bool + } + + -- Notifications @@ -116,14 +127,30 @@ saveBookmark bookmark = -- Queries +objectQueryFromScope : Scope -> Session -> ObjectQuery.Query +objectQueryFromScope scope session = + if scope == Scope.Veli then + session.queries.veli + + else + session.queries.object + + updateFoodQuery : FoodQuery.Query -> Session -> Session updateFoodQuery foodQuery ({ queries } as session) = { session | queries = { queries | food = foodQuery } } -updateObjectQuery : ObjectQuery.Query -> Session -> Session -updateObjectQuery objectQuery ({ queries } as session) = - { session | queries = { queries | object = objectQuery } } +updateObjectQuery : Scope -> ObjectQuery.Query -> Session -> Session +updateObjectQuery scope objectQuery ({ queries } as session) = + { session + | queries = + if scope == Scope.Veli then + { queries | veli = objectQuery } + + else + { queries | object = objectQuery } + } updateTextileQuery : TextileQuery.Query -> Session -> Session diff --git a/src/Main.elm b/src/Main.elm index ef08161ad..2d3254dfa 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -36,8 +36,7 @@ import Views.Page as Page type alias Flags = { clientUrl : String - , enableFoodSection : Bool - , enableObjectSection : Bool + , enabledSections : Session.EnabledSections , matomo : { host : String, siteId : String } , rawStore : String } @@ -145,8 +144,7 @@ setupSession navKey flags db = { clientUrl = flags.clientUrl , currentVersion = Request.Version.Unknown , db = db - , enableFoodSection = flags.enableFoodSection - , enableObjectSection = flags.enableObjectSection + , enabledSections = flags.enabledSections , matomo = flags.matomo , navKey = navKey , notifications = [] @@ -158,6 +156,7 @@ setupSession navKey flags db = |> Example.findByName "Tshirt coton (150g) - Majorant par défaut" |> Result.map .query |> Result.withDefault TextileQuery.default + , veli = ObjectQuery.default } , releases = RemoteData.NotAsked , store = store @@ -228,16 +227,16 @@ setRoute url ( { state } as model, cmds ) = Home.init session |> toPage HomePage HomeMsg - Just (Route.ObjectSimulator trigram maybeQuery) -> - ObjectSimulator.init trigram maybeQuery session + Just (Route.ObjectSimulator scope trigram maybeQuery) -> + ObjectSimulator.init scope trigram maybeQuery session |> toPage ObjectSimulatorPage ObjectSimulatorMsg - Just (Route.ObjectSimulatorExample uuid) -> - ObjectSimulator.initFromExample session uuid + Just (Route.ObjectSimulatorExample scope uuid) -> + ObjectSimulator.initFromExample session scope uuid |> toPage ObjectSimulatorPage ObjectSimulatorMsg - Just Route.ObjectSimulatorHome -> - ObjectSimulator.init Impact.default Nothing session + Just (Route.ObjectSimulatorHome scope) -> + ObjectSimulator.init scope Impact.default Nothing session |> toPage ObjectSimulatorPage ObjectSimulatorMsg Just Route.Stats -> @@ -531,7 +530,7 @@ view { mobileNavigationOpened, state } = ObjectSimulatorPage simulatorModel -> ObjectSimulator.view session simulatorModel |> mapMsg ObjectSimulatorMsg - |> Page.frame (pageConfig Page.Object) + |> Page.frame (pageConfig (Page.Object simulatorModel.scope)) StatsPage statsModel -> Stats.view session statsModel diff --git a/src/Page/Explore.elm b/src/Page/Explore.elm index c6303a732..3413643c0 100644 --- a/src/Page/Explore.elm +++ b/src/Page/Explore.elm @@ -160,10 +160,15 @@ update session msg model = Dataset.FoodExamples Nothing Scope.Object -> + -- FIXME: meubles examples only Dataset.ObjectExamples Nothing Scope.Textile -> Dataset.TextileExamples Nothing + + Scope.Veli -> + -- FIXME: veli examples only + Dataset.ObjectExamples Nothing ) |> Route.Explore scope |> Route.toString @@ -195,9 +200,9 @@ datasetsMenuView { scope, dataset } = scopesMenuView : Session -> Model -> Html Msg -scopesMenuView { enableFoodSection, enableObjectSection } model = - [ ( Scope.Food, enableFoodSection ) - , ( Scope.Object, enableObjectSection ) +scopesMenuView { enabledSections } model = + [ ( Scope.Food, enabledSections.food ) + , ( Scope.Object, enabledSections.objects ) , ( Scope.Textile, True ) ] |> List.filter Tuple.second diff --git a/src/Page/Explore/ObjectExamples.elm b/src/Page/Explore/ObjectExamples.elm index 08b183d37..e9c81344c 100644 --- a/src/Page/Explore/ObjectExamples.elm +++ b/src/Page/Explore/ObjectExamples.elm @@ -3,7 +3,7 @@ module Page.Explore.ObjectExamples exposing (table) import Data.Dataset as Dataset import Data.Example exposing (Example) import Data.Object.Query exposing (Query) -import Data.Scope exposing (Scope) +import Data.Scope as Scope exposing (Scope) import Data.Uuid as Uuid import Html exposing (..) import Html.Attributes exposing (..) @@ -27,9 +27,19 @@ table { maxScore } { detailed, scope } = , toValue = Table.StringValue (Tuple.first >> .name) , toCell = Tuple.first >> .name >> text } + , { label = "Famille" + , toValue = Table.StringValue (Tuple.first >> .scope >> Scope.toLabel) + , toCell = Tuple.first >> .scope >> Scope.toLabel >> text + } , { label = "Catégorie" , toValue = Table.StringValue (Tuple.first >> .category) - , toCell = Tuple.first >> .category >> text + , toCell = + \( { category }, _ ) -> + if category == "" then + i [ class "text-muted" ] [ text "non-renseigné" ] + + else + text category } , { label = "Coût Environnemental" , toValue = Table.FloatValue (Tuple.second >> .score) @@ -40,11 +50,13 @@ table { maxScore } { detailed, scope } = , { label = "" , toValue = Table.NoValue , toCell = - \( { id, name }, _ ) -> + \( example, _ ) -> a [ class "btn btn-light btn-sm w-100" - , Route.href <| Route.ObjectSimulatorExample id - , title <| "Charger " ++ name + + -- FIXME: multiple exlorer for Veli + , Route.href <| Route.ObjectSimulatorExample example.scope example.id + , title <| "Charger " ++ example.name ] [ Icon.search ] } diff --git a/src/Page/Food.elm b/src/Page/Food.elm index 07d7dd9ca..2fea0f9c7 100644 --- a/src/Page/Food.elm +++ b/src/Page/Food.elm @@ -355,6 +355,7 @@ update ({ db, queries } as session) msg model = { name = String.trim name , query = foodQuery , created = now + , subScope = Nothing } , Cmd.none ) diff --git a/src/Page/Home.elm b/src/Page/Home.elm index b2272ffe1..a9b1928ae 100644 --- a/src/Page/Home.elm +++ b/src/Page/Home.elm @@ -69,7 +69,7 @@ update session msg model = viewHero : Session -> Modal -> Html Msg -viewHero { enableFoodSection } modal = +viewHero { enabledSections } modal = Container.centered [ class "pt-4 pb-5" ] [ div [ class "px-5" ] [ h2 [ class "h1" ] @@ -81,7 +81,7 @@ viewHero { enableFoodSection } modal = |> Markdown.simple [] ] , div [ class "d-flex flex-column flex-sm-row gap-3 mb-4" ] - [ if enableFoodSection then + [ if enabledSections.food then button [ class "btn btn-lg btn-primary", onClick OpenCalculatorPickerModal ] [ text "Lancer le calculateur" ] diff --git a/src/Page/Object.elm b/src/Page/Object.elm index 1a68f6b81..90c3c6c30 100644 --- a/src/Page/Object.elm +++ b/src/Page/Object.elm @@ -14,13 +14,13 @@ import Browser.Events import Browser.Navigation as Navigation import Data.Bookmark as Bookmark exposing (Bookmark) import Data.Dataset as Dataset -import Data.Example as Example +import Data.Example as Example exposing (Example) import Data.Impact.Definition as Definition exposing (Definition) import Data.Key as Key import Data.Object.Process as Process exposing (Process) import Data.Object.Query as Query exposing (Query) import Data.Object.Simulator as Simulator exposing (Results) -import Data.Scope as Scope +import Data.Scope as Scope exposing (Scope) import Data.Session as Session exposing (Session) import Data.Uuid exposing (Uuid) import Html exposing (..) @@ -50,10 +50,12 @@ type alias Model = , bookmarkName : String , bookmarkTab : BookmarkView.ActiveTab , comparisonType : ComparatorView.ComparisonType + , examples : List (Example Query) , impact : Definition , initialQuery : Query , modal : Modal , results : Results + , scope : Scope } @@ -86,17 +88,21 @@ type Msg | UpdateItem Query.Item -init : Definition.Trigram -> Maybe Query -> Session -> ( Model, Session, Cmd Msg ) -init trigram maybeUrlQuery session = +init : Scope -> Definition.Trigram -> Maybe Query -> Session -> ( Model, Session, Cmd Msg ) +init scope trigram maybeUrlQuery session = let initialQuery = -- If we received a serialized query from the URL, use it -- Otherwise, fallback to use session query maybeUrlQuery - |> Maybe.withDefault session.queries.object + |> Maybe.withDefault (Session.objectQueryFromScope scope session) + + examples = + session.db.object.examples + |> Example.forScope scope in ( { activeImpactsTab = ImpactTabs.StepImpactsTab - , bookmarkName = initialQuery |> suggestBookmarkName session + , bookmarkName = initialQuery |> suggestBookmarkName session examples , bookmarkTab = BookmarkView.SaveTab , comparisonType = if Session.isAuthenticated session then @@ -104,15 +110,17 @@ init trigram maybeUrlQuery session = else ComparatorView.Steps + , examples = examples , impact = Definition.get trigram session.db.definitions , initialQuery = initialQuery , modal = NoModal , results = Simulator.compute session.db initialQuery |> Result.withDefault Simulator.emptyResults + , scope = scope } , session - |> Session.updateObjectQuery initialQuery + |> Session.updateObjectQuery scope initialQuery , case maybeUrlQuery of -- If we do have an URL query, we either come from a bookmark, a saved simulation click or -- we're tweaking params for the current simulation: we shouldn't reposition the viewport. @@ -126,37 +134,43 @@ init trigram maybeUrlQuery session = ) -initFromExample : Session -> Uuid -> ( Model, Session, Cmd Msg ) -initFromExample session uuid = +initFromExample : Session -> Scope -> Uuid -> ( Model, Session, Cmd Msg ) +initFromExample session scope uuid = let - example = + examples = session.db.object.examples + |> Example.forScope scope + + example = + examples |> Example.findByUuid uuid exampleQuery = example |> Result.map .query - |> Result.withDefault session.queries.object + |> Result.withDefault (Session.objectQueryFromScope scope session) in ( { activeImpactsTab = ImpactTabs.StepImpactsTab - , bookmarkName = exampleQuery |> suggestBookmarkName session + , bookmarkName = exampleQuery |> suggestBookmarkName session examples , bookmarkTab = BookmarkView.SaveTab , comparisonType = ComparatorView.Subscores + , examples = examples , impact = Definition.get Definition.Ecs session.db.definitions , initialQuery = exampleQuery , modal = NoModal , results = Simulator.compute session.db exampleQuery |> Result.withDefault Simulator.emptyResults + , scope = scope } , session - |> Session.updateObjectQuery exampleQuery + |> Session.updateObjectQuery scope exampleQuery , Ports.scrollTo { x = 0, y = 0 } ) -suggestBookmarkName : Session -> Query -> String -suggestBookmarkName { db, store } query = +suggestBookmarkName : Session -> List (Example Query) -> Query -> String +suggestBookmarkName { db, store } examples query = let -- Existing user bookmark? userBookmark = @@ -165,7 +179,7 @@ suggestBookmarkName { db, store } query = -- Matching product example name? exampleName = - db.object.examples + examples |> Example.findByQuery query |> Result.toMaybe in @@ -187,22 +201,23 @@ updateQuery query ( model, session, commands ) = | initialQuery = query , bookmarkName = query - |> suggestBookmarkName session + |> suggestBookmarkName session model.examples , results = query |> Simulator.compute session.db |> Result.withDefault Simulator.emptyResults } - , session |> Session.updateObjectQuery query + , session |> Session.updateObjectQuery model.scope query , commands ) update : Session -> Msg -> Model -> ( Model, Session, Cmd Msg ) -update ({ queries, navKey } as session) msg model = +update ({ navKey } as session) msg model = let query = - queries.object + session + |> Session.objectQueryFromScope model.scope in case ( msg, model.modal ) of ( AddItem item, _ ) -> @@ -257,7 +272,12 @@ update ({ queries, navKey } as session) msg model = , Time.now |> Task.perform (SaveBookmarkWithTime model.bookmarkName - (Bookmark.Object query) + (if model.scope == Scope.Veli then + Bookmark.Veli query + + else + Bookmark.Object query + ) ) ) @@ -268,6 +288,7 @@ update ({ queries, navKey } as session) msg model = { name = String.trim name , query = objectQuery , created = now + , subScope = Just model.scope } , Cmd.none ) @@ -309,7 +330,7 @@ update ({ queries, navKey } as session) msg model = ( model , session , Just query - |> Route.ObjectSimulator trigram + |> Route.ObjectSimulator model.scope trigram |> Route.toString |> Navigation.pushUrl navKey ) @@ -372,27 +393,28 @@ simulatorView session model = [ h1 [ class "visually-hidden" ] [ text "Simulateur " ] , div [ class "sticky-md-top bg-white pb-3" ] [ ExampleView.view - { currentQuery = session.queries.object + { currentQuery = session |> Session.objectQueryFromScope model.scope , emptyQuery = Query.default - , examples = session.db.object.examples + , examples = model.examples , helpUrl = Nothing , onOpen = SelectExampleModal >> SetModal , routes = - -- FIXME: explore route - { explore = Route.Explore Scope.Textile (Dataset.TextileExamples Nothing) - , load = Route.ObjectSimulatorExample - , scopeHome = Route.ObjectSimulatorHome + -- FIXME: explore route object/veli + { explore = Route.Explore model.scope (Dataset.ObjectExamples Nothing) + , load = Route.ObjectSimulatorExample model.scope + , scopeHome = Route.ObjectSimulatorHome model.scope } } ] - , session.queries.object + , session + |> Session.objectQueryFromScope model.scope |> itemListView session.db model.impact model.results |> div [ class "card shadow-sm mb-3" ] ] , div [ class "col-lg-4 bg-white" ] [ SidebarView.view { session = session - , scope = Scope.Object + , scope = model.scope -- Impact selector , selectedImpact = model.impact @@ -457,6 +479,7 @@ itemListView db selectedImpact results query = [ h2 [ class "h5 mb-0" ] [ text "Éléments" , Link.smallPillExternal + -- FIXME: link to Veli explorer? [ Route.href (Route.Explore Scope.Object (Dataset.ObjectProcesses Nothing)) , title "Explorer" , attribute "aria-label" "Explorer" @@ -596,8 +619,8 @@ view session model = , onAutocompleteSelect = OnAutocompleteSelect , placeholderText = "tapez ici le nom du produit pour le rechercher" , title = "Sélectionnez un produit" - , toLabel = Example.toName session.db.object.examples - , toCategory = Example.toCategory session.db.object.examples + , toLabel = Example.toName model.examples + , toCategory = Example.toCategory model.examples } ] ] diff --git a/src/Page/Textile.elm b/src/Page/Textile.elm index 976ef075a..0c0acb91b 100644 --- a/src/Page/Textile.elm +++ b/src/Page/Textile.elm @@ -403,6 +403,7 @@ update ({ queries, navKey } as session) msg model = { name = String.trim name , query = foodQuery , created = now + , subScope = Nothing } , Cmd.none ) diff --git a/src/Route.elm b/src/Route.elm index e5a42626b..6453bc267 100644 --- a/src/Route.elm +++ b/src/Route.elm @@ -30,9 +30,9 @@ type Route | FoodBuilderExample Uuid | FoodBuilderHome | Home - | ObjectSimulator Definition.Trigram (Maybe ObjectQuery.Query) - | ObjectSimulatorExample Uuid - | ObjectSimulatorHome + | ObjectSimulator Scope Definition.Trigram (Maybe ObjectQuery.Query) + | ObjectSimulatorExample Scope Uuid + | ObjectSimulatorHome Scope | Stats | TextileSimulator Definition.Trigram (Maybe TextileQuery.Query) | TextileSimulatorExample Uuid @@ -63,11 +63,13 @@ parser = Dataset.FoodExamples Nothing Scope.Object -> - -- FIXME: object process examples page - Dataset.TextileExamples Nothing + Dataset.ObjectExamples Nothing Scope.Textile -> Dataset.TextileExamples Nothing + + Scope.Veli -> + Dataset.ObjectExamples Nothing ) ) , Parser.map Explore @@ -91,14 +93,14 @@ parser = ) -- Object specific routes - , Parser.map ObjectSimulatorHome + , Parser.map (ObjectSimulatorHome Scope.Object) (Parser.s "object" Parser.s "simulator") - , Parser.map ObjectSimulator <| + , Parser.map (ObjectSimulator Scope.Object) <| Parser.s "object" Parser.s "simulator" Impact.parseTrigram ObjectQuery.parseBase64Query - , Parser.map ObjectSimulatorExample + , Parser.map (ObjectSimulatorExample Scope.Object) (Parser.s "object" Parser.s "edit-example" Example.parseUuid @@ -113,6 +115,20 @@ parser = Parser.s "edit-example" Example.parseUuid ) + + -- Veli specific routes + , Parser.map (ObjectSimulatorHome Scope.Veli) + (Parser.s "veli" Parser.s "simulator") + , Parser.map (ObjectSimulator Scope.Veli) <| + Parser.s "veli" + Parser.s "simulator" + Impact.parseTrigram + ObjectQuery.parseBase64Query + , Parser.map (ObjectSimulatorExample Scope.Veli) + (Parser.s "veli" + Parser.s "edit-example" + Example.parseUuid + ) ] @@ -236,24 +252,24 @@ toString route = Home -> [] - ObjectSimulator trigram (Just query) -> - [ "object" + ObjectSimulator scope trigram (Just query) -> + [ Scope.toString scope , "simulator" , Definition.toString trigram , ObjectQuery.b64encode query ] - ObjectSimulator trigram Nothing -> - [ "object" + ObjectSimulator scope trigram Nothing -> + [ Scope.toString scope , "simulator" , Definition.toString trigram ] - ObjectSimulatorExample uuid -> - [ "object", "edit-example", Uuid.toString uuid ] + ObjectSimulatorExample scope uuid -> + [ Scope.toString scope, "edit-example", Uuid.toString uuid ] - ObjectSimulatorHome -> - [ "object", "simulator" ] + ObjectSimulatorHome scope -> + [ Scope.toString scope, "simulator" ] Stats -> [ "stats" ] diff --git a/src/Views/Bookmark.elm b/src/Views/Bookmark.elm index 2d38ba658..b59e8449c 100644 --- a/src/Views/Bookmark.elm +++ b/src/Views/Bookmark.elm @@ -68,40 +68,58 @@ shareTabView { copyToClipBoard, impact, scope, session } = ( shareableLink, apiCall, jsonParams ) = case scope of Scope.Food -> - ( Just session.queries.food + let + query = + session.queries.food + in + ( Just query |> Route.FoodBuilder impact.trigram |> Route.toString |> (++) session.clientUrl - , session.queries.food - |> FoodQuery.buildApiQuery session.clientUrl - , session.queries.food - |> FoodQuery.encode + , FoodQuery.buildApiQuery session.clientUrl query + , FoodQuery.encode query |> Encode.encode 2 ) Scope.Object -> - ( Just session.queries.object - |> Route.ObjectSimulator impact.trigram + let + query = + session.queries.object + in + ( Just query + |> Route.ObjectSimulator scope impact.trigram |> Route.toString |> (++) session.clientUrl - -- FIXME: make this a maybe - , session.queries.object - |> ObjectQuery.buildApiQuery session.clientUrl - -- FIXME: make this a maybe - , session.queries.object - |> ObjectQuery.encode + , ObjectQuery.buildApiQuery scope session.clientUrl query + , ObjectQuery.encode query |> Encode.encode 2 ) Scope.Textile -> - ( Just session.queries.textile + let + query = + session.queries.textile + in + ( Just query |> Route.TextileSimulator impact.trigram |> Route.toString |> (++) session.clientUrl - , session.queries.textile - |> TextileQuery.buildApiQuery session.clientUrl - , session.queries.textile - |> TextileQuery.encode + , TextileQuery.buildApiQuery session.clientUrl query + , TextileQuery.encode query + |> Encode.encode 2 + ) + + Scope.Veli -> + let + query = + session.queries.veli + in + ( Just query + |> Route.ObjectSimulator scope impact.trigram + |> Route.toString + |> (++) session.clientUrl + , ObjectQuery.buildApiQuery scope session.clientUrl query + , ObjectQuery.encode query |> Encode.encode 2 ) in @@ -209,10 +227,10 @@ managerView cfg = bookmarksView : ManagerConfig msg -> Html msg -bookmarksView cfg = +bookmarksView ({ compare, scope, session } as cfg) = let bookmarks = - scopedBookmarks cfg.session cfg.scope + scopedBookmarks session scope in div [] [ div [ class "card-header border-top rounded-0 d-flex justify-content-between align-items-center" ] @@ -221,7 +239,7 @@ bookmarksView cfg = [ class "btn btn-sm btn-primary" , title "Comparer vos simulations sauvegardées" , disabled (List.isEmpty bookmarks) - , onClick cfg.compare + , onClick compare ] [ span [ class "me-1" ] [ Icon.stats ] , text "Comparer" @@ -229,6 +247,18 @@ bookmarksView cfg = ] , bookmarks |> Bookmark.sort + |> List.filter + (\{ subScope } -> + case subScope of + Just Scope.Object -> + scope == Scope.Object + + Just Scope.Veli -> + scope == Scope.Veli + + _ -> + True + ) |> List.map (bookmarkView cfg) |> ul [ class "list-group list-group-flush rounded-bottom overflow-auto" @@ -251,11 +281,15 @@ bookmarkView cfg ({ name, query } as bookmark) = Bookmark.Object objectQuery -> Just objectQuery - |> Route.ObjectSimulator cfg.impact.trigram + |> Route.ObjectSimulator Scope.Object cfg.impact.trigram Bookmark.Textile textileQuery -> Just textileQuery |> Route.TextileSimulator cfg.impact.trigram + + Bookmark.Veli veliQuery -> + Just veliQuery + |> Route.ObjectSimulator Scope.Veli cfg.impact.trigram in li [ class "list-group-item d-flex justify-content-between align-items-center gap-1 fs-7" @@ -296,6 +330,9 @@ queryFromScope session scope = Scope.Textile -> Bookmark.Textile session.queries.textile + Scope.Veli -> + Bookmark.Veli session.queries.veli + scopedBookmarks : Session -> Scope -> List Bookmark scopedBookmarks session scope = @@ -310,5 +347,8 @@ scopedBookmarks session scope = Scope.Textile -> Bookmark.isTextile + + Scope.Veli -> + Bookmark.isVeli ) |> Bookmark.sort diff --git a/src/Views/Comparator.elm b/src/Views/Comparator.elm index ee45e8913..9416525c2 100644 --- a/src/Views/Comparator.elm +++ b/src/Views/Comparator.elm @@ -150,6 +150,20 @@ addToComparison session label query = } ) + Bookmark.Veli objectQuery -> + objectQuery + |> ObjectSimulator.compute session.db + |> Result.map + (\results -> + { complementsImpact = Impact.noComplementsImpacts + , impacts = ObjectSimulator.extractImpacts results + , label = label + , stepsImpacts = + results + |> ObjectSimulator.toStepsImpacts Definition.Ecs + } + ) + comparatorView : Config msg -> List (Html msg) comparatorView config = diff --git a/src/Views/Format.elm b/src/Views/Format.elm index c0bf31929..00b856a7a 100644 --- a/src/Views/Format.elm +++ b/src/Views/Format.elm @@ -88,6 +88,10 @@ formatFloat decimals float = else if float == 0 then "0" + else if toFloat (round float) == float then + -- flpat is an int, no need for decimals + simpleFmt 0 float + else if abs float >= 100 then simpleFmt 0 float diff --git a/src/Views/Page.elm b/src/Views/Page.elm index 537f67eed..0a7bf8869 100644 --- a/src/Views/Page.elm +++ b/src/Views/Page.elm @@ -10,7 +10,7 @@ import Browser exposing (Document) import Data.Dataset as Dataset import Data.Env as Env import Data.Github as Github -import Data.Scope as Scope +import Data.Scope as Scope exposing (Scope) import Data.Session as Session exposing (Session) import Html exposing (..) import Html.Attributes exposing (..) @@ -33,7 +33,7 @@ type ActivePage | Explore | FoodBuilder | Home - | Object + | Object Scope | Other | Stats | TextileSimulator @@ -133,20 +133,25 @@ newVersionAlert { session, reloadPage } = mainMenuLinks : Session -> List MenuLink -mainMenuLinks { enableFoodSection, enableObjectSection } = +mainMenuLinks { enabledSections } = + let + addRouteIf flag route = + if flag then + Just route + + else + Nothing + in List.filterMap identity [ Just <| Internal "Accueil" Route.Home Home - , Just <| Internal "Textile" Route.TextileSimulatorHome TextileSimulator - , if enableFoodSection then - Just <| Internal "Alimentaire" Route.FoodBuilderHome FoodBuilder - - else - Nothing - , if enableObjectSection then - Just <| Internal "Objets" Route.ObjectSimulatorHome Object - - else - Nothing + , addRouteIf enabledSections.textile <| + Internal "Textile" Route.TextileSimulatorHome TextileSimulator + , addRouteIf enabledSections.food <| + Internal "Alimentaire" Route.FoodBuilderHome FoodBuilder + , addRouteIf enabledSections.objects <| + Internal "Objets" (Route.ObjectSimulatorHome Scope.Object) (Object Scope.Object) + , addRouteIf enabledSections.veli <| + Internal "Véhicules intermédiaires" (Route.ObjectSimulatorHome Scope.Veli) (Object Scope.Veli) , Just <| Internal "Explorateur" (Route.Explore Scope.Textile (Dataset.TextileExamples Nothing)) Explore , Just <| Internal "API" Route.Api Api , Just <| MailTo "Contact" Env.contactEmail diff --git a/tests/Views/FormatTest.elm b/tests/Views/FormatTest.elm index 58344882c..171e5fb37 100644 --- a/tests/Views/FormatTest.elm +++ b/tests/Views/FormatTest.elm @@ -19,8 +19,8 @@ suite = |> asTest "should format zero" , 5 |> Format.formatFloat 2 - |> Expect.equal "5,00" - |> asTest "should not format an int rendering a specific number of 0 decimals" + |> Expect.equal "5" + |> asTest "should format an integer with no decimals" , 5.02 |> Format.formatFloat 2 |> Expect.equal "5,02"