diff --git a/openapi.yaml b/openapi.yaml index 194b32c4d..99c19a6ae 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -23,6 +23,11 @@ tags: description: | Documentation de l'API pour le domaine alimentaire au format OpenAPI + **⚠️ En construction, les résultats fournis sont incomplets et probablement invalides.** + - name: Objet + description: | + Documentation de l'API pour le domaine objet au format OpenAPI + **⚠️ En construction, les résultats fournis sont incomplets et probablement invalides.** externalDocs: description: À propos @@ -449,6 +454,27 @@ paths: application/json: schema: $ref: "#/components/schemas/InvalidParametersError" + + /object/simulator: + get: + tags: + - Objet + summary: Calcul des impacts environnementaux d'un objet + parameters: + - $ref: "#/components/parameters/itemsParam" + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/GenericImpactsResponse" + 400: + description: Paramètres invalides + content: + application/json: + schema: + $ref: "#/components/schemas/InvalidParametersError" components: securitySchemes: token: @@ -1112,6 +1138,33 @@ components: type: number minimum: 0.67 maximum: 1.45 + + itemsParam: + name: "items[]" + in: query + description: | + Liste des procédés composant le produit. + Le format de chaque entrée est composé de la quantité du procédé et de son identifiant. + + - `0.00088;07e9e916-e02b-45e2-a298-2b5084de6242` signifie *0.00088m³ de planche de bois*; + - `1.645313;3295b2a5-328a-4c00-b046-e2ddeb0da823` signifie *1.645313kg de composant en plastique*; + + > Par exemple, les paramètres à passer pour une chaise avec 0.00088m3 de planche de bois et 1.645313kg de composant plastique sont : + > + > ```js + > items[]=0.00088;07e9e916-e02b-45e2-a298-2b5084de6242&items[]=1.645313;3295b2a5-328a-4c00-b046-e2ddeb0da823 + > ``` + required: true + style: form + schema: + type: array + items: + type: string + examples: + table: + value: ["0.002;07e9e916-e02b-45e2-a298-2b5084de6242"] + chaise: + value: ["0.00088;07e9e916-e02b-45e2-a298-2b5084de6242","1.645313;3295b2a5-328a-4c00-b046-e2ddeb0da823"] schemas: Impacts: type: object diff --git a/src/Data/Bookmark.elm b/src/Data/Bookmark.elm index a6f809f37..42fa18567 100644 --- a/src/Data/Bookmark.elm +++ b/src/Data/Bookmark.elm @@ -4,8 +4,10 @@ module Data.Bookmark exposing , decode , encode , findByFoodQuery + , findByObjectQuery , findByTextileQuery , isFood + , isObject , isTextile , sort , toId @@ -14,6 +16,7 @@ module Data.Bookmark exposing import Data.Food.Query as FoodQuery import Data.Food.Recipe as Recipe +import Data.Object.Query as ObjectQuery import Data.Scope as Scope exposing (Scope) import Data.Textile.Inputs as Inputs import Data.Textile.Query as TextileQuery @@ -32,6 +35,7 @@ type alias Bookmark = type Query = Food FoodQuery.Query + | Object ObjectQuery.Query | Textile TextileQuery.Query @@ -47,6 +51,7 @@ decodeQuery : Decoder Query decodeQuery = Decode.oneOf [ Decode.map Food FoodQuery.decode + , Decode.map Object ObjectQuery.decode , Decode.map Textile TextileQuery.decode ] @@ -66,6 +71,9 @@ encodeQuery v = Food query -> FoodQuery.encode query + Object query -> + ObjectQuery.encode query + Textile query -> TextileQuery.encode query @@ -80,6 +88,16 @@ isFood { query } = False +isObject : Bookmark -> Bool +isObject { query } = + case query of + Object _ -> + True + + _ -> + False + + isTextile : Bookmark -> Bool isTextile { query } = case query of @@ -101,6 +119,11 @@ findByFoodQuery foodQuery = findByQuery (Food foodQuery) +findByObjectQuery : ObjectQuery.Query -> List Bookmark -> Maybe Bookmark +findByObjectQuery objectQuery = + findByQuery (Object objectQuery) + + findByTextileQuery : TextileQuery.Query -> List Bookmark -> Maybe Bookmark findByTextileQuery textileQuery = findByQuery (Textile textileQuery) @@ -112,6 +135,9 @@ scope bookmark = Food _ -> Scope.Food + Object _ -> + Scope.Object + Textile _ -> Scope.Textile @@ -135,6 +161,10 @@ toQueryDescription db bookmark = |> Result.map Recipe.toString |> Result.withDefault bookmark.name + Object objectQuery -> + objectQuery + |> ObjectQuery.toString + Textile textileQuery -> textileQuery |> Inputs.fromQuery db diff --git a/src/Data/Object/Process.elm b/src/Data/Object/Process.elm index 6c6710023..869cc2d25 100644 --- a/src/Data/Object/Process.elm +++ b/src/Data/Object/Process.elm @@ -6,6 +6,8 @@ module Data.Object.Process exposing , encode , encodeId , findById + , idFromString + , idToString ) import Data.Impact as Impact exposing (Impacts) @@ -84,3 +86,9 @@ findById processes id = idToString : Id -> String idToString (Id uuid) = Uuid.toString uuid + + +idFromString : String -> Maybe Id +idFromString string = + Uuid.fromString string + |> Maybe.map Id diff --git a/src/Data/Object/Query.elm b/src/Data/Object/Query.elm index 465eeb459..05f44a514 100644 --- a/src/Data/Object/Query.elm +++ b/src/Data/Object/Query.elm @@ -5,11 +5,14 @@ module Data.Object.Query exposing , amount , amountToFloat , b64encode + , buildApiQuery , decode , default , defaultItem + , encode , parseBase64Query , removeItem + , toString , updateItem ) @@ -45,6 +48,17 @@ amountToFloat (Amount float) = float +buildApiQuery : String -> Query -> String +buildApiQuery 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 "%json%" (encode query |> Encode.encode 0) + + decode : Decoder Query decode = Decode.map Query @@ -106,6 +120,23 @@ updateItem newItem query = } +toString : Query -> String +toString query = + query.items + |> List.map + (\i -> + (i.amount + |> amountToFloat + |> String.fromFloat + ) + ++ " " + ++ (i.processId + |> Process.idToString + ) + ) + |> String.join " - " + + -- Parser diff --git a/src/Data/Object/Simulator.elm b/src/Data/Object/Simulator.elm index e98a877d6..097ee3b99 100644 --- a/src/Data/Object/Simulator.elm +++ b/src/Data/Object/Simulator.elm @@ -5,20 +5,20 @@ module Data.Object.Simulator exposing ) import Data.Impact as Impact exposing (Impacts) -import Data.Object.Db exposing (Db) import Data.Object.Process as Process exposing (Process) import Data.Object.Query as Query exposing (Item, Query) import Quantity import Result.Extra as RE +import Static.Db exposing (Db) availableProcesses : Db -> Query -> List Process -availableProcesses db query = +availableProcesses { object } query = let usedIds = List.map .processId query.items in - db.processes + object.processes |> List.filter (\{ id } -> not (List.member id usedIds)) @@ -31,9 +31,9 @@ compute db query = computeItemImpacts : Db -> Item -> Result String Impacts -computeItemImpacts db { amount, processId } = +computeItemImpacts { object } { amount, processId } = processId - |> Process.findById db.processes + |> Process.findById object.processes |> Result.map (.impacts >> Impact.mapImpacts (\_ -> Quantity.multiplyBy (Query.amountToFloat amount)) diff --git a/src/Page/Object.elm b/src/Page/Object.elm index 0e545f46e..849321ef3 100644 --- a/src/Page/Object.elm +++ b/src/Page/Object.elm @@ -12,12 +12,12 @@ import Autocomplete exposing (Autocomplete) import Browser.Dom as Dom 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.Impact as Impact import Data.Impact.Definition as Definition exposing (Definition) import Data.Key as Key -import Data.Object.Db exposing (Db) import Data.Object.Process as Process exposing (Process) import Data.Object.Query as Query exposing (Query) import Data.Object.Simulator as Simulator @@ -31,37 +31,55 @@ import Ports import Quantity import Result.Extra as RE import Route +import Static.Db exposing (Db) import Task +import Time exposing (Posix) import Views.AutocompleteSelector as AutocompleteSelectorView import Views.Bookmark as BookmarkView +import Views.Comparator as ComparatorView import Views.Container as Container import Views.Example as ExampleView import Views.Format as Format import Views.Icon as Icon +import Views.Modal as ModalView import Views.Sidebar as SidebarView type alias Model = - { initialQuery : Query + { bookmarkName : String + , bookmarkTab : BookmarkView.ActiveTab + , comparisonType : ComparatorView.ComparisonType , impact : Definition + , initialQuery : Query , modal : Modal } type Modal - = NoModal + = ComparatorModal + | NoModal | SelectExampleModal (Autocomplete Query) type Msg = AddItem Query.Item | CopyToClipBoard String + | DeleteBookmark Bookmark | NoOp | OnAutocompleteExample (Autocomplete.Msg Query) | OnAutocompleteSelect + | OpenComparator | RemoveItem Process.Id + | SaveBookmark + | SaveBookmarkWithTime String Bookmark.Query Posix + | SelectAllBookmarks + | SelectNoBookmarks | SetModal Modal + | SwitchBookmarksTab BookmarkView.ActiveTab + | SwitchComparisonType ComparatorView.ComparisonType | SwitchImpact (Result String Definition.Trigram) + | ToggleComparedSimulation Bookmark Bool + | UpdateBookmarkName String | UpdateItem Query.Item @@ -74,8 +92,16 @@ init trigram maybeUrlQuery session = maybeUrlQuery |> Maybe.withDefault session.queries.object in - ( { initialQuery = initialQuery + ( { bookmarkName = initialQuery |> findExistingBookmarkName session + , bookmarkTab = BookmarkView.SaveTab + , comparisonType = + if Session.isAuthenticated session then + ComparatorView.Subscores + + else + ComparatorView.Steps , impact = Definition.get trigram session.db.definitions + , initialQuery = initialQuery , modal = NoModal } , session @@ -105,8 +131,11 @@ initFromExample session uuid = |> Result.map .query |> Result.withDefault session.queries.object in - ( { initialQuery = exampleQuery + ( { bookmarkName = exampleQuery |> findExistingBookmarkName session + , bookmarkTab = BookmarkView.SaveTab + , comparisonType = ComparatorView.Subscores , impact = Definition.get Definition.Ecs session.db.definitions + , initialQuery = exampleQuery , modal = NoModal } , session @@ -115,6 +144,14 @@ initFromExample session uuid = ) +findExistingBookmarkName : Session -> Query -> String +findExistingBookmarkName { store } query = + store.bookmarks + |> Bookmark.findByObjectQuery query + |> Maybe.map .name + |> Maybe.withDefault (query |> Query.toString) + + updateQuery : Query -> ( Model, Session, Cmd Msg ) -> ( Model, Session, Cmd Msg ) updateQuery query ( model, session, commands ) = ( { model | initialQuery = query } @@ -137,6 +174,12 @@ update ({ queries, navKey } as session) msg model = ( CopyToClipBoard shareableLink, _ ) -> ( model, session, Ports.copyToClipboard shareableLink ) + ( DeleteBookmark bookmark, _ ) -> + ( model + , session |> Session.deleteBookmark bookmark + , Cmd.none + ) + ( NoOp, _ ) -> ( model, session, Cmd.none ) @@ -162,10 +205,49 @@ update ({ queries, navKey } as session) msg model = ( OnAutocompleteSelect, _ ) -> ( model, session, Cmd.none ) + ( OpenComparator, _ ) -> + ( { model | modal = ComparatorModal } + , session |> Session.checkComparedSimulations + , Cmd.none + ) + ( RemoveItem processId, _ ) -> ( model, session, Cmd.none ) |> updateQuery (Query.removeItem processId query) + ( SaveBookmark, _ ) -> + ( model + , session + , Time.now + |> Task.perform + (SaveBookmarkWithTime model.bookmarkName + (Bookmark.Object query) + ) + ) + + ( SaveBookmarkWithTime name objectQuery now, _ ) -> + ( model + , session + |> Session.saveBookmark + { name = String.trim name + , query = objectQuery + , created = now + } + , Cmd.none + ) + + ( SelectAllBookmarks, _ ) -> + ( model, Session.selectAllBookmarks session, Cmd.none ) + + ( SelectNoBookmarks, _ ) -> + ( model, Session.selectNoBookmarks session, Cmd.none ) + + ( SetModal ComparatorModal, _ ) -> + ( { model | modal = ComparatorModal } + , session + , Ports.addBodyClass "prevent-scrolling" + ) + ( SetModal NoModal, _ ) -> ( { model | modal = NoModal } , session @@ -178,6 +260,15 @@ update ({ queries, navKey } as session) msg model = , Ports.addBodyClass "prevent-scrolling" ) + ( SwitchBookmarksTab bookmarkTab, _ ) -> + ( { model | bookmarkTab = bookmarkTab } + , session + , Cmd.none + ) + + ( SwitchComparisonType displayChoice, _ ) -> + ( { model | comparisonType = displayChoice }, session, Cmd.none ) + ( SwitchImpact (Ok trigram), _ ) -> ( model , session @@ -193,6 +284,15 @@ update ({ queries, navKey } as session) msg model = , Cmd.none ) + ( ToggleComparedSimulation bookmark checked, _ ) -> + ( model + , session |> Session.toggleComparedSimulation bookmark checked + , Cmd.none + ) + + ( UpdateBookmarkName newName, _ ) -> + ( { model | bookmarkName = newName }, session, Cmd.none ) + ( UpdateItem item, _ ) -> ( model, session, Cmd.none ) |> updateQuery (Query.updateItem item query) @@ -267,13 +367,13 @@ simulatorView session model = } ] , session.queries.object - |> itemListView session.db.object model.impact + |> itemListView session.db model.impact |> div [ class "d-flex flex-column bg-white" ] ] , div [ class "col-lg-4 bg-white" ] [ SidebarView.view { session = session - , scope = Scope.Textile + , scope = Scope.Object -- Impact selector , selectedImpact = model.impact @@ -283,21 +383,21 @@ simulatorView session model = , customScoreInfo = Nothing , productMass = Quantity.zero , totalImpacts = - Simulator.compute session.db.object session.queries.object + Simulator.compute session.db session.queries.object |> Result.withDefault Impact.empty -- Impacts tabs , impactTabsConfig = Nothing - -- FIXME: bookmarks - , activeBookmarkTab = BookmarkView.ShareTab - , bookmarkName = "" + -- Bookmarks + , activeBookmarkTab = model.bookmarkTab + , bookmarkName = model.bookmarkName , copyToClipBoard = CopyToClipBoard - , compareBookmarks = NoOp - , deleteBookmark = always NoOp - , saveBookmark = NoOp - , updateBookmarkName = always NoOp - , switchBookmarkTab = always NoOp + , compareBookmarks = OpenComparator + , deleteBookmark = DeleteBookmark + , saveBookmark = SaveBookmark + , updateBookmarkName = UpdateBookmarkName + , switchBookmarkTab = SwitchBookmarksTab } ] ] @@ -344,7 +444,7 @@ itemListView db selectedImpact query = case query.items |> List.map (\{ amount, processId } -> ( amount, processId )) - |> List.map (RE.combineMapSecond (Process.findById db.processes)) + |> List.map (RE.combineMapSecond (Process.findById db.object.processes)) |> RE.combine of Err error -> @@ -413,6 +513,28 @@ view session model = , [ Container.centered [ class "Simulator pb-3" ] [ simulatorView session model , case model.modal of + ComparatorModal -> + ModalView.view + { size = ModalView.ExtraLarge + , close = SetModal NoModal + , noOp = NoOp + , title = "Comparateur de simulations sauvegardées" + , subTitle = Just "en score d'impact, par produit" + , formAction = Nothing + , content = + [ ComparatorView.view + { session = session + , impact = model.impact + , comparisonType = model.comparisonType + , selectAll = SelectAllBookmarks + , selectNone = SelectNoBookmarks + , switchComparisonType = SwitchComparisonType + , toggle = ToggleComparedSimulation + } + ] + , footer = [] + } + NoModal -> text "" diff --git a/src/Server.elm b/src/Server.elm index 8eb04f5c4..e23fece59 100644 --- a/src/Server.elm +++ b/src/Server.elm @@ -11,14 +11,16 @@ import Data.Food.Origin as Origin import Data.Food.Process as FoodProcess import Data.Food.Query as BuilderQuery import Data.Food.Recipe as BuilderRecipe -import Data.Impact as Impact +import Data.Impact as Impact exposing (Impacts) import Data.Impact.Definition as Definition +import Data.Object.Query as ObjectQuery +import Data.Object.Simulator as ObjectSimulator import Data.Scope as Scope import Data.Textile.Inputs as Inputs import Data.Textile.Material as Material exposing (Material) import Data.Textile.Product as TextileProduct exposing (Product) import Data.Textile.Query as TextileQuery -import Data.Textile.Simulator as Simulator exposing (Simulator) +import Data.Textile.Simulator as TextileSimulator import Json.Encode as Encode import Route as WebRoute import Server.Query as Query @@ -75,8 +77,8 @@ toResponse encodedResult = ( 200, encoded ) -toAllImpactsSimple : Simulator -> Encode.Value -toAllImpactsSimple { impacts, inputs } = +toAllTextileImpactsSimple : TextileSimulator.Simulator -> Encode.Value +toAllTextileImpactsSimple { impacts, inputs } = Encode.object [ ( "webUrl", serverRootUrl ++ toTextileWebUrl Nothing inputs |> Encode.string ) , ( "impacts", Impact.encode impacts ) @@ -85,6 +87,14 @@ toAllImpactsSimple { impacts, inputs } = ] +toAllObjectImpactsSimple : Impacts -> Encode.Value +toAllObjectImpactsSimple impacts = + Encode.object + [ -- FIXME: add webUrl/description/query + ( "impacts", Impact.encode impacts ) + ] + + toFoodWebUrl : Definition.Trigram -> BuilderQuery.Query -> String toFoodWebUrl trigram foodQuery = Just foodQuery @@ -99,7 +109,7 @@ toTextileWebUrl maybeTrigram textileQuery = |> WebRoute.toString -toSingleImpactSimple : Definition.Trigram -> Simulator -> Encode.Value +toSingleImpactSimple : Definition.Trigram -> TextileSimulator.Simulator -> Encode.Value toSingleImpactSimple trigram { impacts, inputs } = Encode.object [ ( "webUrl", serverRootUrl ++ toTextileWebUrl (Just trigram) inputs |> Encode.string ) @@ -128,9 +138,16 @@ executeFoodQuery db encoder = >> toResponse -executeTextileQuery : Db -> (Simulator -> Encode.Value) -> TextileQuery.Query -> JsonResponse +executeTextileQuery : Db -> (TextileSimulator.Simulator -> Encode.Value) -> TextileQuery.Query -> JsonResponse executeTextileQuery db encoder = - Simulator.compute db + TextileSimulator.compute db + >> Result.map encoder + >> toResponse + + +executeObjectQuery : Db -> (Impacts -> Encode.Value) -> ObjectQuery.Query -> JsonResponse +executeObjectQuery db encoder = + ObjectSimulator.compute db >> Result.map encoder >> toResponse @@ -253,7 +270,7 @@ handleRequest db request = Just (Route.TextileGetSimulator (Ok query)) -> query - |> executeTextileQuery db toAllImpactsSimple + |> executeTextileQuery db toAllTextileImpactsSimple Just (Route.TextileGetSimulator (Err errors)) -> Query.encodeErrors errors @@ -261,7 +278,7 @@ handleRequest db request = Just (Route.TextileGetSimulatorDetailed (Ok query)) -> query - |> executeTextileQuery db Simulator.encode + |> executeTextileQuery db TextileSimulator.encode Just (Route.TextileGetSimulatorDetailed (Err errors)) -> Query.encodeErrors errors @@ -275,6 +292,14 @@ handleRequest db request = Query.encodeErrors errors |> respondWith 400 + Just (Route.ObjectGetSimulator (Ok query)) -> + query + |> executeObjectQuery db toAllObjectImpactsSimple + + Just (Route.ObjectGetSimulator (Err errors)) -> + Query.encodeErrors errors + |> respondWith 400 + -- POST routes Just (Route.FoodPostRecipe (Ok foodQuery)) -> executeFoodQuery db (toFoodResults foodQuery) foodQuery @@ -285,7 +310,7 @@ handleRequest db request = Just (Route.TextilePostSimulator (Ok textileQuery)) -> textileQuery - |> executeTextileQuery db toAllImpactsSimple + |> executeTextileQuery db toAllTextileImpactsSimple Just (Route.TextilePostSimulator (Err error)) -> Encode.string error @@ -293,7 +318,7 @@ handleRequest db request = Just (Route.TextilePostSimulatorDetailed (Ok textileQuery)) -> textileQuery - |> executeTextileQuery db Simulator.encode + |> executeTextileQuery db TextileSimulator.encode Just (Route.TextilePostSimulatorDetailed (Err error)) -> Encode.string error diff --git a/src/Server/Query.elm b/src/Server/Query.elm index 68e2420ed..e80a9b6f4 100644 --- a/src/Server/Query.elm +++ b/src/Server/Query.elm @@ -2,6 +2,7 @@ module Server.Query exposing ( Errors , encodeErrors , parseFoodQuery + , parseObjectQuery , parseTextileQuery ) @@ -11,8 +12,10 @@ import Data.Food.Db as Food import Data.Food.Ingredient as Ingredient exposing (Ingredient) import Data.Food.Preparation as Preparation import Data.Food.Process as FoodProcess -import Data.Food.Query as BuilderQuery +import Data.Food.Query as FoodQuery import Data.Food.Retail as Retail exposing (Distribution) +import Data.Object.Process as ObjectProcess +import Data.Object.Query as ObjectQuery import Data.Scope as Scope exposing (Scope) import Data.Split as Split exposing (Split) import Data.Textile.DyeingMedium as DyeingMedium exposing (DyeingMedium) @@ -63,9 +66,48 @@ succeed = always >> Query.custom "" -parseFoodQuery : Db -> Parser (Result Errors BuilderQuery.Query) +parseObjectQuery : Parser (Result Errors ObjectQuery.Query) +parseObjectQuery = + succeed (Ok ObjectQuery.Query) + |> apply (itemListParser "items") + + +itemListParser : String -> Parser (ParseResult (List ObjectQuery.Item)) +itemListParser key = + Query.custom (key ++ "[]") + (List.map itemParser + >> RE.combine + >> Result.mapError (\err -> ( key, err )) + ) + + +itemParser : String -> Result String ObjectQuery.Item +itemParser string = + case String.split ";" string of + [ amount, id ] -> + Ok ObjectQuery.Item + |> RE.andMap + (amount + |> String.toFloat + |> Maybe.map ObjectQuery.amount + |> Result.fromMaybe ("Format de quantité invalide, nombre flottant attendu\u{202F}: " ++ amount ++ ".") + ) + |> RE.andMap + (id + |> ObjectProcess.idFromString + |> Result.fromMaybe ("Format de process invalide, UUID attendu\u{202F}: " ++ id ++ ".") + ) + + [ "" ] -> + Err <| "Format d'item vide." + + _ -> + Err <| "Format d'item invalide : " ++ string ++ "." + + +parseFoodQuery : Db -> Parser (Result Errors FoodQuery.Query) parseFoodQuery { countries, food } = - succeed (Ok BuilderQuery.Query) + succeed (Ok FoodQuery.Query) |> apply (distributionParser "distribution") |> apply (ingredientListParser "ingredients" countries food) |> apply (packagingListParser "packaging" food.processes) @@ -73,7 +115,7 @@ parseFoodQuery { countries, food } = |> apply (maybeTransformParser "transform" food.processes) -ingredientListParser : String -> List Country -> Food.Db -> Parser (ParseResult (List BuilderQuery.IngredientQuery)) +ingredientListParser : String -> List Country -> Food.Db -> Parser (ParseResult (List FoodQuery.IngredientQuery)) ingredientListParser key countries food = Query.custom (key ++ "[]") (List.map (ingredientParser countries food) @@ -82,7 +124,7 @@ ingredientListParser key countries food = ) -ingredientParser : List Country -> Food.Db -> String -> Result String BuilderQuery.IngredientQuery +ingredientParser : List Country -> Food.Db -> String -> Result String FoodQuery.IngredientQuery ingredientParser countries food string = let byPlaneParser byPlane ingredient = @@ -97,7 +139,7 @@ ingredientParser countries food string = food.ingredients |> Ingredient.findByID (Ingredient.idFromString id) in - Ok BuilderQuery.IngredientQuery + Ok FoodQuery.IngredientQuery |> RE.andMap (Ok Nothing) |> RE.andMap (Result.map .id ingredient) |> RE.andMap (validateMassInGrams mass) @@ -109,7 +151,7 @@ ingredientParser countries food string = food.ingredients |> Ingredient.findByID (Ingredient.idFromString id) in - Ok BuilderQuery.IngredientQuery + Ok FoodQuery.IngredientQuery |> RE.andMap (countryParser countries Scope.Food countryCode) |> RE.andMap (Result.map .id ingredient) |> RE.andMap (validateMassInGrams mass) @@ -121,7 +163,7 @@ ingredientParser countries food string = food.ingredients |> Ingredient.findByID (Ingredient.idFromString id) in - Ok BuilderQuery.IngredientQuery + Ok FoodQuery.IngredientQuery |> RE.andMap (countryParser countries Scope.Food countryCode) |> RE.andMap (Result.map .id ingredient) |> RE.andMap (validateMassInGrams mass) @@ -152,7 +194,7 @@ foodProcessCodeParser processes string = |> Result.map .identifier -packagingListParser : String -> List FoodProcess.Process -> Parser (ParseResult (List BuilderQuery.ProcessQuery)) +packagingListParser : String -> List FoodProcess.Process -> Parser (ParseResult (List FoodQuery.ProcessQuery)) packagingListParser key packagings = Query.custom (key ++ "[]") (List.map (packagingParser packagings) @@ -179,11 +221,11 @@ preparationListParser key = ) -packagingParser : List FoodProcess.Process -> String -> Result String BuilderQuery.ProcessQuery +packagingParser : List FoodProcess.Process -> String -> Result String FoodQuery.ProcessQuery packagingParser packagings string = case String.split ";" string of [ code, mass ] -> - Ok BuilderQuery.ProcessQuery + Ok FoodQuery.ProcessQuery |> RE.andMap (foodProcessCodeParser packagings code) |> RE.andMap (validateMassInGrams mass) @@ -285,7 +327,7 @@ validatePhysicalDurability string = ) -maybeTransformParser : String -> List FoodProcess.Process -> Parser (ParseResult (Maybe BuilderQuery.ProcessQuery)) +maybeTransformParser : String -> List FoodProcess.Process -> Parser (ParseResult (Maybe FoodQuery.ProcessQuery)) maybeTransformParser key transforms = Query.string key |> Query.map @@ -368,11 +410,11 @@ maybeIntParser key = ) -parseTransform_ : List FoodProcess.Process -> String -> Result String BuilderQuery.ProcessQuery +parseTransform_ : List FoodProcess.Process -> String -> Result String FoodQuery.ProcessQuery parseTransform_ transforms string = case String.split ";" string of [ code, mass ] -> - Ok BuilderQuery.ProcessQuery + Ok FoodQuery.ProcessQuery |> RE.andMap (foodProcessCodeParser transforms code) |> RE.andMap (validateMassInGrams mass) diff --git a/src/Server/Route.elm b/src/Server/Route.elm index 6124d5951..2f3c97ebc 100644 --- a/src/Server/Route.elm +++ b/src/Server/Route.elm @@ -6,6 +6,7 @@ module Server.Route exposing import Data.Food.Query as BuilderQuery import Data.Impact as Impact import Data.Impact.Definition as Definition +import Data.Object.Query as ObjectQuery import Data.Textile.Inputs as Inputs import Data.Textile.Query as TextileQuery import Json.Decode as Decode @@ -39,6 +40,11 @@ type Route -- POST -- Food recipe builder (POST, JSON body) | FoodPostRecipe (Result String BuilderQuery.Query) + -- + -- Object Routes + -- GET + -- Textile Simple version of all impacts (GET, query string) + | ObjectGetSimulator (Result Query.Errors ObjectQuery.Query) -- -- Textile Routes -- GET @@ -100,6 +106,10 @@ parser db body = |> Parser.map (TextilePostSimulatorDetailed (decodeTextileQueryBody db body)) , (s "POST" s "textile" s "simulator" Impact.parseTrigram) |> Parser.map (TextilePostSimulatorSingle (decodeTextileQueryBody db body)) + + -- Object + , (s "GET" s "object" s "simulator" Query.parseObjectQuery) + |> Parser.map ObjectGetSimulator ] diff --git a/src/Views/Bookmark.elm b/src/Views/Bookmark.elm index 7362d0452..2d38ba658 100644 --- a/src/Views/Bookmark.elm +++ b/src/Views/Bookmark.elm @@ -3,6 +3,7 @@ module Views.Bookmark exposing (ActiveTab(..), view) import Data.Bookmark as Bookmark exposing (Bookmark) import Data.Food.Query as FoodQuery import Data.Impact.Definition exposing (Definition) +import Data.Object.Query as ObjectQuery import Data.Scope as Scope exposing (Scope) import Data.Session exposing (Session) import Data.Textile.Query as TextileQuery @@ -84,11 +85,11 @@ shareTabView { copyToClipBoard, impact, scope, session } = |> Route.toString |> (++) session.clientUrl -- FIXME: make this a maybe - , session.queries.textile - |> TextileQuery.buildApiQuery session.clientUrl + , session.queries.object + |> ObjectQuery.buildApiQuery session.clientUrl -- FIXME: make this a maybe - , session.queries.textile - |> TextileQuery.encode + , session.queries.object + |> ObjectQuery.encode |> Encode.encode 2 ) @@ -248,6 +249,10 @@ bookmarkView cfg ({ name, query } as bookmark) = Just foodQuery |> Route.FoodBuilder cfg.impact.trigram + Bookmark.Object objectQuery -> + Just objectQuery + |> Route.ObjectSimulator cfg.impact.trigram + Bookmark.Textile textileQuery -> Just textileQuery |> Route.TextileSimulator cfg.impact.trigram @@ -286,8 +291,7 @@ queryFromScope session scope = Bookmark.Food session.queries.food Scope.Object -> - -- FIXME: object bookmarks - Bookmark.Textile session.queries.textile + Bookmark.Object session.queries.object Scope.Textile -> Bookmark.Textile session.queries.textile @@ -302,8 +306,7 @@ scopedBookmarks session scope = Bookmark.isFood Scope.Object -> - -- FIXME: object bookmarks - always False + Bookmark.isObject Scope.Textile -> Bookmark.isTextile diff --git a/src/Views/Comparator.elm b/src/Views/Comparator.elm index 9f61a0c1f..86ac00b70 100644 --- a/src/Views/Comparator.elm +++ b/src/Views/Comparator.elm @@ -7,8 +7,9 @@ import Data.Bookmark as Bookmark exposing (Bookmark) import Data.Food.Recipe as Recipe import Data.Impact as Impact import Data.Impact.Definition as Definition exposing (Definition, Definitions) +import Data.Object.Simulator as ObjectSimulator import Data.Session as Session exposing (Session) -import Data.Textile.Simulator as Simulator +import Data.Textile.Simulator as TextileSimulator import Data.Unit as Unit import Dict import Html exposing (..) @@ -121,9 +122,21 @@ addToComparison session label query = } ) + Bookmark.Object objectQuery -> + objectQuery + |> ObjectSimulator.compute session.db + |> Result.map + (\_ -> + { complementsImpact = Impact.noComplementsImpacts + , impacts = Impact.empty + , label = label + , stepsImpacts = Impact.noStepsImpacts + } + ) + Bookmark.Textile textileQuery -> textileQuery - |> Simulator.compute session.db + |> TextileSimulator.compute session.db |> Result.map (\simulator -> { complementsImpact = simulator.complementsImpacts @@ -131,7 +144,7 @@ addToComparison session label query = , label = label , stepsImpacts = simulator - |> Simulator.toStepsImpacts Definition.Ecs + |> TextileSimulator.toStepsImpacts Definition.Ecs } ) diff --git a/tests/Data/Object/SimulatorTest.elm b/tests/Data/Object/SimulatorTest.elm index f3cbc2cb0..d5b875ccc 100644 --- a/tests/Data/Object/SimulatorTest.elm +++ b/tests/Data/Object/SimulatorTest.elm @@ -14,7 +14,7 @@ import TestUtils exposing (asTest, suiteWithDb) getEcsImpact : Db -> Query -> Result String Float getEcsImpact db = - Simulator.compute db.object + Simulator.compute db >> Result.map (Impact.getImpact Definition.Ecs >> Unit.impactToFloat)