Flake support/frontend (#324)

* Setup flake info extraction

Prepare data model fro derivations (#1)

Add flake info data (#1)

Implement fetching general flake info (#1)

Expose CLI (#1)

Keep cargo happy

Add some doc comments

Pin to local nixpkgs to excessive downloads

Extend visibility of some data objects

Add command to extract infomation about defivations (#1)

Add call new feature in main (#1)

Include more information in derivation (#1)

Add log access

Always debug log stderr from nix

Format nix script

Collect systems per package

Remove unnecessary imports

Create flake

Remove top level version field

Represent collected systems/version pairs in rust

Fix quotation marks in tests

Add correct cargo hash

Add iconv dependency

Return a list from nix script

Export as json

Undo version by platform distinction

Remove nixpkgs override

Apply cargo fmt

Flatten export structure

Allow for complex licenses

Prepare using a central nix file

Implement nix part o accessing apps

Include the correct filename

Add accessor for `all` key

Access all available information by default

Track more information about Apps

Run cargo fmt

Fix: allow local builds

Prepare next version of the flake info tool

Include examples and pull script

Expose flake info as library

Include thiserror for custom errors

Define a source data type

Collects source types and their metadata, collected in a json file

Add command line argument for input files

Mutually exclusive with --flake

Refactor functions to extract information given a flake identifier

Add kind specifier as CLI argument

Amend Argument parsing to require eiteher flake or targets to be defined

Run extraction for specified flake or list of flakes as specified in a json file

Resolves #5
References #7

Use internal tag to distnguich target types

Include target falg usage in examples

Set include provided source if available (resolves #9)

Resolve flake name

Update examples

Dont include empty license or description

Fix a misfomatting in cargot.toml

Add elastic dependencies

Implement a wrapper around the elasticsearch client

Implements pushing exports (#4)

Temporarily skip serializing an unimplemented field in elastic output

Extract reading source list files from binary

Add lazy_static as dependency

Implement createing and pushing to elastic index

Add elastic options

Provide default name and env falbac for elastic index

Modify app binary and type as optionals

App can be a derivation too

Update examples

Add more elastic commands

Supported:
- ensure
- clear
- push

Rename elastic search config struct

Add elastic push support to binary

Rename flag to enable elastic push

Imporve error messages and format binary source

Fix nix file incorrectly expecting meta fields

Changing flake descriotions to an optional field

deserialize git_ref as hash

Implement temporary stores and gc of these

prevents flakes from accessing store paths

Pass extra arguments to nix

Update cargo hash and skip integration tests

Move flake.nix to root folder and add apps for all components

Fix command invocation that fails test

Update README(s)

Add help for extra arguments

(cherry picked from commit be4bc3dd929178bef66114c2201aaa88e47e9add)

* Safely read legacyPackages

* Read nixosOptions from flake

* Update ES Mapping

* Show more detailed error and backtrace if available

* Try reading options only if key  is defined

* Format nix script

* Add error context when attempting deserialization

* Fix derivation representation to fit nix output

* Add push elasticsearch settings

* Add Flake channel

* Rename import module

* Remove Flakes Channel

* Prepare nixpkgs import

* Separate import/export types

* Break up nixpkgs package representation

* Use the same naming scheme for Nixpkgs entries and flake entries

* Document import module

* Remove serialization attributes

* Reversable type and SerDe implemetation

* Add *_reverse fields

* Unpublicating export fields

* Read from  NixOption struct

* serialize empty fields as null

* Tag export json variants

* Serialize a single option-declaration

* Format npkgs parse test

* Define NixOption

Sorry thats too late..

* Parse system key

* Make Package output compatible with the frontend

* Add Url-only licesnse variant

* Add StringOrStruct type

* Add accessor method for elements catched by OneOrMany

* New utility to flatten in homogenous lists recursively

* Add Maintainer type catching maintaiers used in nixpkgs

* Format Implementatio n

* Remove explicit representation of platforms

* Open nixpkgs parser to cover all packages

* Convert all imported fields to their export representation

* Define reverse fields in ES schema

* Format nixpkgs command runner

* Expose shorthand to pull a specific channel by command line

* Extract utility functions into their own module

* Implement AttributeQuery generation

* Integrate query in export

* Implement *_set attributes

* Document purpose of export module

* Use more descriptive github blob route

* Complete Option Export representation

Reuse the same option type for nixpkgs and flakes

* Enable nixpkgs option import

* Expose nixpkgs option import functions and integrate in binary

* Chunk ElasticSearch Bulk operations

* Address Example/Default field formatting

* Add abort strategy for existing indices

* New command line interface using subcommands

* Document new interface

* Bump version

* Add nixpkgs cron job

* More precise name for nixpkgs cron job

* Add Flakes cron job

* Read version from file and fix channel names

* Correct file names for flake group import

* Fix group command

* Run new cron jobs on PR

* Update Cargo sources

* Integrate new flakes route

* Add search type option

* Add flake types and messages

* Add flake types

* Extract Request body builders and expose more types

* Combine Package and Option Search

* Factor out html body

* Dispatch messages by search type to flake page

* Correct type naming

* Remove Debug instructions and unimplemented flake search type

* Do not reload Flake search page while the search subject is unchanged

* Implement switching subjects

* Fix init type signature

* Add url/git based flakes to mapping

* Parse flake info

* Link to flake repo and show flake maintainer

* Fix optional decoded values

* Add group and type as search buckts

* Show search selection in all cases

* Move flake decoding to search

* Show flake information for options

* Hardcode flakesearch to only search a specified flake index

* Improve experimental state notice

* Fix category select not present in some cases

* Change default flake index

* Show correct category title

* Add missing imports

* Serialize more optional fields as null

* elastic-test.rs file not needed anymore

* names of the workflows should be immediately obvious.

* better jobs names

* rename the flakes group

* need to provide --elastic-schema-version via the cli option

* typo

* Fix errors in workflow files

* Install flake enabled nix by default

* Fix variable substitution

* Use string group names

* Provide elastic schema verion only through cli

* Fix scheduled task name

* Improve error reporting

* Allow insecure packages here

* Add missing imports

* Tree-wide: cargo-fmt

* only import nixpkgs for now

* no importing of flakes

* also bump a version

* fixing cron-flakes.yml

* make it obvious that this is for frontend

* missed this when merging

* Extract hashes

- Split out revision info from flakes
- Retrieve current nixpkgs revision from github

* Write aliases, enabled by default

* Filter additional platforms

* Change alias structure

* expect channel like nixpkgs identifiers

* Don't cause error if push is aborted

* Specify correct channel identifier

* Allow options to evaluate unfree packages

* Retrieve and delete specific aliases

* Specify import path correclty

* Fix channel warmup

* Abort push if channel already indexed

* Remove debug pr hook for import action

* Fix indentation by tab

* Make flakes show again

* Fix import group naming in flake wrokflow

* Rename flake group to match imported index

* Run nixpkgs import on pr activity (Debugging behavior)

* Just show literal Examples, resolves #336

* Use actual nixpkgs branch names

* Trim derivation/option declaration path, resolves #335

Remove /nix/store/*-source prefix

* Fix sidebar width and close button position

* Placeholder texts in flake result area

* Show flake install info

* Don't show package/option selection before search

* Make sure install command for nixos is always shown

* Add toml source config support

* Rewrite current example flake group as toml

* Update flake cron job

* flake-info: use saner nix packaging method

When trying to first work in this I naive approached it with
`nix-shell`. That of course lead to the fixed output bollocks failing
with a hash mismatch. By making use of the `cargoLock` attribute on
`buildRustPackage` we can tame the FOD-beast and only have to provide
one hash manually for a single package (that we fetch from a GitHub
repository).

This also means that updating dependencies will be simpler as the native
Cargo.lock file can be used.

(cherry picked from commit c3a0e46d1eb56e128e6923e6c493eb836fc81e85)

* Update flake lock file

* Do not build python import script

* add flake names to the title as well

* Disable debug imports on prs

Co-authored-by: Rok Garbas <rok@garbas.si>
Co-authored-by: Andreas Rammhold <andreas@rammhold.de>
This commit is contained in:
Yannik Sander 2021-08-26 00:40:42 +02:00 committed by GitHub
parent 5ad71362e5
commit af3b494217
Failed to generate hash of commit
25 changed files with 1334 additions and 562 deletions

69
.github/workflows/cron-flakes.yml vendored Normal file
View file

@ -0,0 +1,69 @@
name: "Flakes: Hourly import to Elasticsearch"
on:
schedule:
- cron: '0 * * * *'
jobs:
hourly-import-channel:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
group:
- "manual"
env:
RUST_LOG: debug
FI_ES_EXISTS_STRATEGY: recreate
FI_ES_URL: ${{ secrets.ELASTICSEARCH_URL }}
steps:
- name: Checking out the repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Installing Nix
uses: cachix/install-nix-action@v13
with:
install_url: https://nixos-nix-install-tests.cachix.org/serve/i6laym9jw3wg9mw6ncyrk6gjx4l34vvx/install
install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve'
extra_nix_config: |
experimental-features = nix-command flakes
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@v10
with:
name: nixos-search
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
- name: Install unstable channel
run: |
nix-channel --add https://nixos.org/channels/nixpkgs-unstable
nix-channel --update
- name: Installing jq
run: |
nix-env -iA nixpkgs.nixFlakes nixpkgs.jq
- name: Building import_scripts
run: |
nix build ./#packages.x86_64-linux.flake_info
- name: Import ${{ matrix.group }} group
run: |
./result/bin/flake-info --push --elastic-schema-version=$(cat ./VERSION) group ./flakes/${{ matrix.group }}.toml ${{ matrix.group }}
if: github.repository == 'NixOS/nixos-search'
- name: Warmup ${{ matrix.group }} channel
run: |
curl ${{ secrets.ELASTICSEARCH_URL }}/$(cat VERSION)-${{ matrix.group }}/_search | jq '.took'
curl ${{ secrets.ELASTICSEARCH_URL }}/$(cat VERSION)-${{ matrix.group }}/_search | jq '.took'
curl ${{ secrets.ELASTICSEARCH_URL }}/$(cat VERSION)-${{ matrix.group }}/_search | jq '.took'
if: github.repository == 'NixOS/nixos-search'

View file

@ -4,6 +4,7 @@ on:
schedule:
- cron: '0 * * * *'
pull_request:
jobs:

View file

@ -1,4 +1,4 @@
name: "Build & Deploy to Netlify"
name: "Frontend: Build & Deploy to Netlify"
on:
pull_request:
push:
@ -34,10 +34,6 @@ jobs:
cat /etc/nix/nix.conf
echo "$HOME/.nix-profile/bin" >> $GITHUB_PATH
- name: Building import_scripts
run: |
nix build ./#packages.x86_64-linux.import_scripts
- name: Building search.nixos.org
run: |
nix build ./#packages.x86_64-linux.frontend

View file

@ -1 +1 @@
21
22

10
flake-info/Cargo.lock generated
View file

@ -308,6 +308,7 @@ dependencies = [
"tempfile",
"thiserror",
"tokio",
"toml",
]
[[package]]
@ -1455,6 +1456,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.1"

View file

@ -10,6 +10,7 @@ edition = "2018"
clap = "^2.33"
serde = {version="1.0", features = ["derive"]}
serde_json = "1.0"
toml = "0.5"
anyhow = "1.0"
thiserror = "1.0"
structopt = "0.3"

View file

@ -1,16 +1,17 @@
{ pkgs ? import <nixpkgs> { } }: with pkgs;
rustPlatform.buildRustPackage rec {
name = "flake-info";
src = ./.;
cargoSha256 = "sha256-TA1WEvmOfnxQ+rRwkIPN1t4VPrDL6pq+WnPHVu2/CPE=";
nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl openssl.dev ] ++ lib.optional pkgs.stdenv.isDarwin [libiconv darwin.apple_sdk.frameworks.Security];
checkFlags = [
"--skip elastic::tests"
"--skip nix_gc::tests"
];
rustPlatform.buildRustPackage {
name = "flake-info";
src = ./.;
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"elasticsearch-8.0.0-alpha.1" = "0x8iw4m16vy6i28mj30aqdwfw4a3hd174l8l9yigddn3cr53cagx";
};
};
nativeBuildInputs = [ pkg-config ];
buildInputs = [ openssl openssl.dev ] ++ lib.optional pkgs.stdenv.isDarwin [ libiconv darwin.apple_sdk.frameworks.Security ];
checkFlags = [
"--skip elastic::tests"
"--skip nix_gc::tests"
];
}

View file

@ -57,7 +57,7 @@ enum Command {
channel: String,
},
Group {
#[structopt(help = "Points to a JSON file containing info targets")]
#[structopt(help = "Points to a TOML or JSON file containing info targets. If file does not end in 'toml' json is assumed")]
targets: PathBuf,
name: String,
@ -217,7 +217,7 @@ async fn run_command(
.await
.map_err(FlakeInfoError::Nixpkgs)?;
let ident = (
"nixpkgs".to_owned(),
"nixos".to_owned(),
nixpkgs.channel.clone(),
nixpkgs.git_ref.clone(),
);
@ -335,7 +335,6 @@ async fn push_to_elastic(
ensure?;
}
es.push_exports(&config, successes)
.await
.with_context(|| "Failed to push results to elasticsearch".to_string())?;

View file

@ -73,14 +73,17 @@ let
cleanUpOption = module: opt:
let
applyOnAttr = n: f: lib.optionalAttrs (lib.hasAttr n opt) { ${n} = f opt.${n}; };
# mkDeclaration = decl: rec {
# path = stripModulePathPrefixes decl;
# url = mkModuleUrl path;
# channelPath = "${channelName}/${path}";
# };
mkDeclaration = decl:
let
discard = lib.concatStringsSep "/" (lib.take 4 (lib.splitString "/" decl));
path = if lib.hasPrefix builtins.storeDir decl then lib.removePrefix discard decl else decl;
in
path;
# Replace functions by the string <function>
substFunction = x:
if builtins.isAttrs x then
if x ? _type && x._type == "literalExample" then x.text
else if builtins.isAttrs x then
lib.mapAttrs (name: substFunction) x
else if builtins.isList x then
map substFunction x
@ -89,19 +92,19 @@ let
else
x;
in
opt
opt
// applyOnAttr "example" substFunction
// applyOnAttr "default" substFunction
// applyOnAttr "type" substFunction
// applyOnAttr "declarations" (map mkDeclaration)
// lib.optionalAttrs (!isNixOS) { flake = [ flake module ]; };
# // applyOnAttr "declarations" (map mkDeclaration)
options = lib.mapAttrs (
attr: module: let
list = lib.optionAttrSetToDocList (declarations module);
in
map (cleanUpOption attr) (lib.filter (x: !x.internal) list )
map (cleanUpOption attr) (lib.filter (x: !x.internal) list)
) modules;
in
lib.flatten (builtins.attrValues options);

View file

@ -217,6 +217,14 @@ impl From<import::NixpkgsEntry> for Derivation {
.map(|m| m.name.to_owned().unwrap())
.collect();
let position: Option<String> = package.meta.position.map(|p| {
if p.starts_with("/nix/store") {
p.split("/").skip(4).collect::<Vec<&str>>().join("/")
} else {
p
}
});
Derivation::Package {
package_attr_name: attribute.clone(),
package_attr_name_reverse: Reverse(attribute.clone()),
@ -247,7 +255,7 @@ impl From<import::NixpkgsEntry> for Derivation {
.meta
.homepage
.map_or(Default::default(), OneOrMany::into_list),
package_position: package.meta.position,
package_position: position,
}
}
import::NixpkgsEntry::Option(option) => option.into(),

View file

@ -7,10 +7,7 @@ use super::Source;
/// Holds general infoamtion about a flake
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Flake {
#[serde(
rename(serialize = "flake_description"),
skip_serializing_if = "Option::is_none"
)]
#[serde(rename(serialize = "flake_description"))]
pub description: Option<String>,
#[serde(rename(serialize = "flake_path"), skip_serializing)]
pub path: PathBuf,

View file

@ -2,7 +2,9 @@ use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::{
fs::{self, File},
io::Read,
path::Path,
ffi::OsStr,
};
pub type Hash = String;
@ -31,6 +33,12 @@ pub enum Source {
Nixpkgs(Nixpkgs),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct TomlDocument {
sources: Vec<Source>
}
impl Source {
pub fn to_flake_ref(&self) -> FlakeRef {
match self {
@ -68,9 +76,19 @@ impl Source {
}
pub fn read_sources_file(path: &Path) -> Result<Vec<Source>> {
let file = File::open(path).with_context(|| "Failed to open input file")?;
Ok(serde_json::from_reader(file)?)
let mut file = File::open(path).with_context(|| "Failed to open input file")?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
if path.extension() == Some(OsStr::new("toml")) {
let document: TomlDocument = toml::from_str(&buf)?;
Ok(document.sources)
}
else {
Ok(serde_json::from_str(&buf)?)
}
}
pub async fn nixpkgs(channel: String) -> Result<Nixpkgs> {

View file

@ -34,6 +34,9 @@ lazy_static! {
"repo": {
"type": "keyword"
},
"url" : {
"type": "keyword"
}
}
},
"flake_source": {

View file

@ -17,11 +17,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1616779317,
"narHash": "sha256-+mUTkYguFMNGb57JkwauDgjcq65RnOLUhDo4mhb8qAI=",
"lastModified": 1629618782,
"narHash": "sha256-2K8SSXu3alo/URI3MClGdDSns6Gb4ZaW4LET53UWyKk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ad47284f8b01f587e24a4f14e0f93126d8ebecda",
"rev": "870959c7fb3a42af1863bed9e1756086a74eb649",
"type": "github"
},
"original": {
@ -51,11 +51,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1617110670,
"narHash": "sha256-+GoJjy1Hlpvs2dFjxJsWHur4Jb8gT0Lig3y2MwRn3cI=",
"lastModified": 1629413283,
"narHash": "sha256-DeyaGvtO/Nureis423Vu4s/X7r0I/h6/ELJLr0sHy2w=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "19bfde46bb9ba980142f2ca99268ef14df8cad5c",
"rev": "f0ef3fee8053eb32207761d6d708e217e2aa3d02",
"type": "github"
},
"original": {

29
flakes/manual.toml Normal file
View file

@ -0,0 +1,29 @@
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "offen"
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "pixelfed"
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "lightmeter"
[[sources]]
type = "github"
owner = "ngi-nix"
repo = "openpgp-ca"
[[sources]]
type = "gitlab"
owner = "pi-lar"
repo = "neuropil"
[[sources]]
type = "github"
owner = "tweag"
repo = "nickel"

View file

@ -26,13 +26,17 @@ import Html.Attributes
, src
, type_
)
import Page.Flakes exposing (Model(..))
import Page.Home
import Page.Options
import Page.Packages
import Page.Flakes
import Route
import Route exposing (SearchType(..))
import Search
import Url
import Search exposing (defaultFlakeId)
import Search exposing (channels)
import Html exposing (sup)
import Html exposing (small)
@ -127,6 +131,7 @@ attemptQuery (( model, _ ) as pair) =
, Cmd.map msg <|
makeRequest
model.elasticsearch
searchModel.searchType
searchModel.channel
(Maybe.withDefault "" searchModel.query)
searchModel.from
@ -140,19 +145,40 @@ attemptQuery (( model, _ ) as pair) =
case model.page of
Packages searchModel ->
if Search.shouldLoad searchModel then
submitQuery PackagesMsg Page.Packages.makeRequest searchModel
submitQuery PackagesMsg Page.Packages.makeRequest { searchModel | searchType = PackageSearch }
else
noEffects pair
Options searchModel ->
if Search.shouldLoad searchModel then
submitQuery OptionsMsg Page.Options.makeRequest searchModel
submitQuery OptionsMsg Page.Options.makeRequest { searchModel | searchType = OptionSearch }
else
noEffects pair
Flakes (OptionModel searchModel) ->
if Search.shouldLoad searchModel then
submitQuery FlakesMsg Page.Flakes.makeRequest {searchModel | channel = defaultFlakeId }
else
noEffects pair
Flakes (PackagesModel searchModel) ->
if Search.shouldLoad searchModel then
-- let
-- _ = Debug.log "main" "submit flake message"
-- in
submitQuery FlakesMsg Page.Flakes.makeRequest {searchModel | channel = defaultFlakeId}
else
-- let _ = Debug.log "main" "should not load flakes" in
noEffects pair
_ ->
-- let
-- _ = Debug.log "pair" <| Debug.toString pair
-- in
pair
@ -171,6 +197,12 @@ pageMatch m1 m2 =
( Options _, Options _ ) ->
True
( Flakes (OptionModel _), Flakes (OptionModel _) ) ->
True
( Flakes (PackagesModel _), Flakes (PackagesModel _) ) ->
True
_ ->
False
@ -239,6 +271,7 @@ changeRouteTo currentModel url =
Route.Flakes searchArgs ->
let
-- _ = Debug.log "changeRouteTo" "flakes"
modelPage =
case model.page of
Flakes x ->
@ -255,6 +288,8 @@ changeRouteTo currentModel url =
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
-- let _ = Debug.log "main" "update"
-- in
case ( msg, model.page ) of
( ClickedLink urlRequest, _ ) ->
case urlRequest of
@ -289,6 +324,10 @@ update msg model =
Page.Options.update model.navKey subMsg subModel
|> updateWith Options OptionsMsg model
( FlakesMsg subMsg, Flakes subModel ) ->
Page.Flakes.update model.navKey subMsg subModel
|> updateWith Flakes FlakesMsg model
( _, _ ) ->
-- Disregard messages that arrived for the wrong page.
( model, Cmd.none )
@ -330,7 +369,7 @@ view model =
[ a [ class "brand", href "https://nixos.org" ]
[ img [ src "https://nixos.org/logo/nix-wiki.png", class "logo" ] []
]
, div [ ]
, div []
[ ul [ class "nav pull-left" ]
(viewNavigation model.route)
]
@ -375,26 +414,30 @@ viewNavigation route =
Route.Options searchArgs ->
f searchArgs
Route.Flakes searchArgs ->
f searchArgs
_ ->
f <| Route.SearchArgs Nothing Nothing Nothing Nothing Nothing Nothing Nothing
f <| Route.SearchArgs Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
in
li [] [ a [ href "https://nixos.org" ] [ text "Back to nixos.org" ] ]
:: List.map
(viewNavigationItem route)
[ ( toRoute Route.Packages, "Packages" )
, ( toRoute Route.Options, "Options" )
--, ( toRoute Route.Flakes, "Flakes (Experimental)" )
[ ( toRoute Route.Packages, text "Packages" )
, ( toRoute Route.Options, text "Options" )
, ( toRoute Route.Flakes, span [] [ text "Flakes", sup [] [span [class "label label-info"][small [] [text "Experimental"]]]] )
]
viewNavigationItem :
Route.Route
-> ( Route.Route, String )
-> ( Route.Route, Html Msg )
-> Html Msg
viewNavigationItem currentRoute ( route, title ) =
li
[ classList [ ( "active", currentRoute == route ) ] ]
[ a [ Route.href route ] [ text title ] ]
[ a [ Route.href route ] [ title ] ]
viewPage : Model -> Html Msg
@ -415,6 +458,8 @@ viewPage model =
Flakes flakesModel ->
Html.map (\m -> FlakesMsg m) <| Page.Flakes.view flakesModel
-- SUBSCRIPTIONS

View file

@ -1,52 +1,65 @@
module Page.Flakes exposing (Model, Msg, init, update, view)
module Page.Flakes exposing (Model(..), Msg(..), init, makeRequest, update, view)
import Browser.Navigation
import Html exposing (Html, a, code, div, li, pre, strong, text, ul)
import Html exposing (Html, a, code, div, li, nav, pre, strong, text, ul)
import Html.Attributes exposing (class, classList, href, target)
import Html.Events exposing (onClick)
import Html.Parser
import Html.Parser.Util
import Json.Decode
import Route
import Http exposing (Body)
import Json.Decode exposing (Decoder)
import Page.Options exposing (Msg(..))
import Page.Packages exposing (Msg(..))
import Route exposing (Route(..), SearchArgs, SearchType(..))
import Search
import View.Components
-- MODEL
type alias Model =
Search.Model ResultItemSource ResultAggregations
type alias ResultItemSource =
{ name : String
, description : Maybe String
, type_ : Maybe String
, default : Maybe String
, example : Maybe String
, source : Maybe String
}
type alias ResultAggregations =
{ all : AggregationsAll
}
type alias AggregationsAll =
{ doc_count : Int
}
type Model
= OptionModel Page.Options.Model
| PackagesModel Page.Packages.Model
init : Route.SearchArgs -> Maybe Model -> ( Model, Cmd Msg )
init searchArgs model =
let
-- _ =
-- Debug.log "Flakes" "init"
-- init with respective module or with packages by default
searchType =
Maybe.withDefault PackageSearch searchArgs.type_
mapEitherModel m =
case ( searchType, m ) of
( OptionSearch, OptionModel model_ ) ->
Tuple.mapBoth OptionModel (Cmd.map OptionsMsg) <| Page.Options.init searchArgs <| Just model_
( PackageSearch, PackagesModel model_ ) ->
Tuple.mapBoth PackagesModel (Cmd.map PackagesMsg) <| Page.Packages.init searchArgs <| Just model_
_ ->
default
default =
case searchType of
PackageSearch ->
Tuple.mapBoth PackagesModel (Cmd.map PackagesMsg) <| Page.Packages.init searchArgs Nothing
OptionSearch ->
Tuple.mapBoth OptionModel (Cmd.map OptionsMsg) <| Page.Options.init searchArgs Nothing
( newModel, newCmd ) =
Search.init searchArgs model
Maybe.withDefault default <| Maybe.map mapEitherModel model
-- _ =
-- Debug.log "mapped Model" <| Maybe.map mapEitherModel model
in
( newModel
, Cmd.map SearchMsg newCmd
, newCmd
)
@ -55,7 +68,8 @@ init searchArgs model =
type Msg
= SearchMsg (Search.Msg ResultItemSource ResultAggregations)
= OptionsMsg Page.Options.Msg
| PackagesMsg Page.Packages.Msg
update :
@ -64,17 +78,43 @@ update :
-> Model
-> ( Model, Cmd Msg )
update navKey msg model =
case msg of
SearchMsg subMsg ->
let
( newModel, newCmd ) =
Search.update
Route.Options
navKey
subMsg
model
in
( newModel, Cmd.map SearchMsg newCmd )
-- let
-- _ =
-- Debug.log "Flake update" ( msg, model )
-- in
case ( msg, model ) of
( OptionsMsg msg_, OptionModel model_ ) ->
case msg_ of
Page.Options.SearchMsg subMsg ->
let
-- _ =
-- Debug.log "update - options"
( newModel, newCmd ) =
Search.update
Route.Flakes
navKey
subMsg
model_
in
( newModel, Cmd.map Page.Options.SearchMsg newCmd ) |> Tuple.mapBoth OptionModel (Cmd.map OptionsMsg)
( PackagesMsg msg_, PackagesModel model_ ) ->
case msg_ of
Page.Packages.SearchMsg subMsg ->
let
-- _ =
-- Debug.log "Flakes" "update - packages"
( newModel, newCmd ) =
Search.update
Route.Flakes
navKey
subMsg
model_
in
( newModel, Cmd.map Page.Packages.SearchMsg newCmd ) |> Tuple.mapBoth PackagesModel (Cmd.map PackagesMsg)
_ ->
( model, Cmd.none )
@ -83,151 +123,26 @@ update navKey msg model =
view : Model -> Html Msg
view model =
Search.view { toRoute = Route.Options, categoryName = "options" }
[ text "Search more than "
, strong [] [ text "10 000 options" ]
]
model
viewSuccess
viewBuckets
SearchMsg
viewBuckets :
Maybe String
-> Search.SearchResult ResultItemSource ResultAggregations
-> List (Html Msg)
viewBuckets _ _ =
[]
viewSuccess :
String
-> Bool
-> Maybe String
-> List (Search.ResultItem ResultItemSource)
-> Html Msg
viewSuccess channel showNixOSDetails show hits =
ul []
(List.map
(viewResultItem channel showNixOSDetails show)
hits
)
viewResultItem :
String
-> Bool
-> Maybe String
-> Search.ResultItem ResultItemSource
-> Html Msg
viewResultItem channel _ show item =
let
showHtml value =
case Html.Parser.run value of
Ok nodes ->
Html.Parser.Util.toVirtualDom nodes
Err _ ->
[]
default =
"Not given"
asPre value =
pre [] [ text value ]
asPreCode value =
div [] [ pre [] [ code [ class "code-block" ] [ text value ] ] ]
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
withEmpty wrapWith maybe =
case maybe of
Nothing ->
asPre default
Just "" ->
asPre default
Just value ->
wrapWith value
wrapped wrapWith value =
case value of
"" ->
wrapWith <| "\"" ++ value ++ "\""
_ ->
wrapWith value
showDetails =
if Just item.source.name == show then
div [ Html.Attributes.map SearchMsg Search.trapClick ]
[ div [] [ text "Name" ]
, div [] [ wrapped asPreCode item.source.name ]
, div [] [ text "Description" ]
, div [] <|
(item.source.description
|> Maybe.map showHtml
|> Maybe.withDefault []
)
, div [] [ text "Default value" ]
, div [] [ withEmpty (wrapped asPreCode) item.source.default ]
, div [] [ text "Type" ]
, div [] [ withEmpty asPre item.source.type_ ]
, div [] [ text "Example" ]
, div [] [ withEmpty (wrapped asPreCode) item.source.example ]
, div [] [ text "Declared in" ]
, div [] [ withEmpty asGithubLink item.source.source ]
mkBody categoryName =
View.Components.body { toRoute = Route.Flakes, categoryName = categoryName }
[ text "Search packages and options of "
, strong []
[ a
[ href "https://github.com/NixOS/nixos-search/blob/main/flakes/manual.toml" ]
[ text "public flakes" ]
]
|> Just
]
else
Nothing
body =
case model of
OptionModel model_ ->
Html.map OptionsMsg <| mkBody "Options" model_ Page.Options.viewSuccess Page.Options.viewBuckets Page.Options.SearchMsg
toggle =
SearchMsg (Search.ShowDetails item.source.name)
isOpen =
Just item.source.name == show
PackagesModel model_ ->
Html.map PackagesMsg <| mkBody "Packages" model_ Page.Packages.viewSuccess Page.Packages.viewBuckets Page.Packages.SearchMsg
in
li
[ class "option"
, classList [ ( "opened", isOpen ) ]
, Search.elementId item.source.name
]
<|
List.filterMap identity
[ Just <|
Html.a
[ class "search-result-button"
, onClick toggle
, href ""
]
[ text item.source.name ]
, showDetails
]
body
@ -236,6 +151,7 @@ viewResultItem channel _ show item =
makeRequest :
Search.Options
-> SearchType
-> String
-> String
-> Int
@ -243,55 +159,50 @@ makeRequest :
-> Maybe String
-> Search.Sort
-> Cmd Msg
makeRequest options channel query from size _ sort =
Search.makeRequest
(Search.makeRequestBody
(String.trim query)
from
size
sort
"option"
"option_name"
[]
[]
[]
"option_name"
[ ( "option_name", 6.0 )
, ( "option_name_query", 3.0 )
, ( "option_description", 1.0 )
]
)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-options")
|> Cmd.map SearchMsg
makeRequest options searchType index_id query from size maybeBuckets sort =
let
cmd =
case searchType of
PackageSearch ->
Search.makeRequest
(makeRequestBody searchType query from size maybeBuckets sort)
index_id
Page.Packages.decodeResultItemSource
Page.Packages.decodeResultAggregations
options
Search.QueryResponse
(Just "query-packages")
|> Cmd.map Page.Packages.SearchMsg
|> Cmd.map PackagesMsg
OptionSearch ->
Search.makeRequest
(makeRequestBody searchType query from size maybeBuckets sort)
index_id
Page.Options.decodeResultItemSource
Page.Options.decodeResultAggregations
options
Search.QueryResponse
(Just "query-options")
|> Cmd.map Page.Options.SearchMsg
|> Cmd.map OptionsMsg
-- FlakeSearch ->
-- Debug.todo "branch 'FlakeSearch' not implemented"
in
cmd
makeRequestBody : SearchType -> String -> Int -> Int -> Maybe String -> Search.Sort -> Body
makeRequestBody searchType query from size maybeBuckets sort =
case searchType of
OptionSearch ->
Page.Options.makeRequestBody query from size sort
PackageSearch ->
Page.Packages.makeRequestBody query from size maybeBuckets sort
-- JSON
decodeResultItemSource : Json.Decode.Decoder ResultItemSource
decodeResultItemSource =
Json.Decode.map6 ResultItemSource
(Json.Decode.field "option_name" Json.Decode.string)
(Json.Decode.field "option_description" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_type" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_default" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_example" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_source" (Json.Decode.nullable Json.Decode.string))
decodeResultAggregations : Json.Decode.Decoder ResultAggregations
decodeResultAggregations =
Json.Decode.map ResultAggregations
(Json.Decode.field "all" decodeResultAggregationsAll)
decodeResultAggregationsAll : Json.Decode.Decoder AggregationsAll
decodeResultAggregationsAll =
Json.Decode.map AggregationsAll
(Json.Decode.field "doc_count" Json.Decode.int)
-- FlakeSearch ->
-- Debug.todo "branch 'FlakeSearch' not implemented"

View file

@ -1,11 +1,17 @@
module Page.Options exposing
( Model
, Msg
, Msg(..)
, ResultAggregations
, ResultItemSource
, decodeResultAggregations
, decodeResultItemSource
, init
, makeRequest
, makeRequestBody
, update
, view
, viewBuckets
, viewSuccess
)
import Browser.Navigation
@ -17,6 +23,8 @@ import Html
, div
, li
, pre
, source
, span
, strong
, text
, ul
@ -34,9 +42,13 @@ import Html.Events
)
import Html.Parser
import Html.Parser.Util
import Http exposing (Body)
import Json.Decode
import Route
import Search
import Json.Decode.Pipeline
import List exposing (sort)
import Route exposing (SearchType)
import Search exposing (Details, decodeResolvedFlake)
import Url.Parser exposing (query)
@ -54,6 +66,12 @@ type alias ResultItemSource =
, default : Maybe String
, example : Maybe String
, source : Maybe String
-- flake
, flake : Maybe ( String, String )
, flakeName : Maybe String
, flakeDescription : Maybe String
, flakeUrl : Maybe String
}
@ -119,6 +137,7 @@ view model =
viewSuccess
viewBuckets
SearchMsg
[]
viewBuckets :
@ -131,21 +150,21 @@ viewBuckets _ _ =
viewSuccess :
String
-> Bool
-> Details
-> Maybe String
-> List (Search.ResultItem ResultItemSource)
-> Html Msg
viewSuccess channel showNixOSDetails show hits =
viewSuccess channel showInstallDetails show hits =
ul []
(List.map
(viewResultItem channel showNixOSDetails show)
(viewResultItem channel showInstallDetails show)
hits
)
viewResultItem :
String
-> Bool
-> Details
-> Maybe String
-> Search.ResultItem ResultItemSource
-> Html Msg
@ -168,28 +187,6 @@ viewResultItem channel _ show item =
asPreCode value =
div [] [ pre [] [ code [ class "code-block" ] [ text value ] ] ]
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
withEmpty wrapWith maybe =
case maybe of
Nothing ->
@ -227,7 +224,7 @@ viewResultItem channel _ show item =
, div [] [ text "Example" ]
, div [] [ withEmpty (wrapped asPreCode) item.source.example ]
, div [] [ text "Declared in" ]
, div [] [ withEmpty asGithubLink item.source.source ]
, div [] <| findSource channel item.source
]
|> Just
@ -239,6 +236,44 @@ viewResultItem channel _ show item =
isOpen =
Just item.source.name == show
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
sourceFile =
Maybe.map asGithubLink item.source.source
flakeOrNixpkgs =
case ( item.source.flakeName, item.source.flake, item.source.flakeUrl ) of
-- its a flake
( Just name, Just ( _, module_ ), Just flakeUrl_ ) ->
Just
[ li []
[ a [ href flakeUrl_ ] [ text name ]
]
]
_ ->
Nothing
in
li
[ class "option"
@ -248,22 +283,78 @@ viewResultItem channel _ show item =
<|
List.filterMap identity
[ Just <|
Html.a
[ class "search-result-button"
, onClick toggle
, href ""
]
[ text item.source.name ]
ul [ class "search-result-button" ]
(List.append
(flakeOrNixpkgs |> Maybe.withDefault [])
[ li []
[ a
[ onClick toggle
, href ""
]
[ text item.source.name ]
]
]
)
, showDetails
]
findSource : String -> ResultItemSource -> List (Html a)
findSource channel source =
let
githubUrlPrefix branch =
"https://github.com/NixOS/nixpkgs/blob/" ++ branch ++ "/"
cleanPosition value =
if String.startsWith "source/" value then
String.dropLeft 7 value
else
value
asGithubLink value =
case Search.channelDetailsFromId channel of
Just channelDetails ->
a
[ href <| githubUrlPrefix channelDetails.branch ++ (value |> String.replace ":" "#L")
, target "_blank"
]
[ text value ]
Nothing ->
text <| cleanPosition value
sourceFile =
Maybe.map asGithubLink source.source
flakeOrNixpkgs : Maybe (List (Html a))
flakeOrNixpkgs =
case ( source.flake, source.flakeUrl ) of
-- its a flake
( Just ( name, module_ ), Just flakeUrl_ ) ->
Just <|
List.append
(Maybe.withDefault [] <| Maybe.map (\sourceFile_ -> [ sourceFile_, span [] [ text " in " ] ]) sourceFile)
[ span [] [ text "Flake: " ]
, a [ href flakeUrl_ ] [ text <| name ++ "(Module: " ++ module_ ++ ")" ]
]
( Nothing, _ ) ->
Maybe.map (\l -> [ l ]) sourceFile
_ ->
Nothing
in
Maybe.withDefault [ span [] [ text "Not Found" ] ] flakeOrNixpkgs
-- API
makeRequest :
Search.Options
-> SearchType
-> String
-> String
-> Int
@ -271,24 +362,9 @@ makeRequest :
-> Maybe String
-> Search.Sort
-> Cmd Msg
makeRequest options channel query from size _ sort =
makeRequest options _ channel query from size _ sort =
Search.makeRequest
(Search.makeRequestBody
(String.trim query)
from
size
sort
"option"
"option_name"
[]
[]
[]
"option_name"
[ ( "option_name", 6.0 )
, ( "option_name_query", 3.0 )
, ( "option_description", 1.0 )
]
)
(makeRequestBody query from size sort)
channel
decodeResultItemSource
decodeResultAggregations
@ -298,19 +374,44 @@ makeRequest options channel query from size _ sort =
|> Cmd.map SearchMsg
makeRequestBody : String -> Int -> Int -> Search.Sort -> Body
makeRequestBody query from size sort =
Search.makeRequestBody
(String.trim query)
from
size
sort
"option"
"option_name"
[]
[]
[]
"option_name"
[ ( "option_name", 6.0 )
, ( "option_name_query", 3.0 )
, ( "option_description", 1.0 )
]
-- JSON
decodeResultItemSource : Json.Decode.Decoder ResultItemSource
decodeResultItemSource =
Json.Decode.map6 ResultItemSource
(Json.Decode.field "option_name" Json.Decode.string)
(Json.Decode.field "option_description" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_type" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_default" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_example" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "option_source" (Json.Decode.nullable Json.Decode.string))
Json.Decode.succeed ResultItemSource
|> Json.Decode.Pipeline.required "option_name" Json.Decode.string
|> Json.Decode.Pipeline.optional "option_description" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_type" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_default" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_example" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_source" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_flake"
(Json.Decode.map Just <| Json.Decode.map2 Tuple.pair (Json.Decode.index 0 Json.Decode.string) (Json.Decode.index 1 Json.Decode.string))
Nothing
|> Json.Decode.Pipeline.optional "flake_name" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_description" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_resolved" (Json.Decode.map Just decodeResolvedFlake) Nothing
decodeResultAggregations : Json.Decode.Decoder ResultAggregations

View file

@ -1,13 +1,20 @@
module Page.Packages exposing
( Model
, Msg
, Msg(..)
, decodeResultAggregations
, decodeResultItemSource
, encodeBuckets
, init
, initBuckets
, makeRequest
, makeRequestBody
, update
, view
, viewBuckets
, viewSuccess
)
import Browser.Events exposing (Visibility(..))
import Browser.Navigation
import Html
exposing
@ -31,16 +38,19 @@ import Html.Attributes
, href
, id
, target
, type_
)
import Html.Events exposing (onClick)
import Json.Decode
import Http exposing (Body)
import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline
import Json.Encode
import Maybe
import Regex
import Route
import Search
import Route exposing (Route(..), SearchType)
import Search exposing (Details(..), channelDetailsFromId, decodeResolvedFlake)
import Utils
import Search exposing (channelDetailsFromId)
import View.Components.SearchInput exposing (closeButton, viewBucket)
@ -64,6 +74,9 @@ type alias ResultItemSource =
, homepage : List String
, system : String
, hydra : Maybe (List ResultPackageHydra)
, flakeName : Maybe String
, flakeDescription : Maybe String
, flakeUrl : Maybe ( String, String )
}
@ -148,20 +161,24 @@ init searchArgs model =
let
( newModel, newCmd ) =
Search.init searchArgs model
-- _ =
-- Debug.log "New package model" newModel
in
( newModel
, Cmd.map SearchMsg newCmd
)
platforms: List String
platforms =
[ "x86_64-linux"
, "aarch64-linux"
, "i686-linux"
, "x86_64-darwin"
, "aarch64-darwin"
]
platforms : List String
platforms =
[ "x86_64-linux"
, "aarch64-linux"
, "i686-linux"
, "x86_64-darwin"
, "aarch64-darwin"
]
-- UPDATE
@ -204,6 +221,7 @@ view model =
viewSuccess
viewBuckets
SearchMsg
[]
viewBuckets :
@ -255,73 +273,32 @@ viewBuckets bucketsAsString result =
selectedBucket.platforms
filterPlatformsBucket : List {a | key : String} -> List {a | key : String}
filterPlatformsBucket = List.filter (\a -> List.member a.key platforms)
viewBucket :
String
-> List Search.AggregationsBucketItem
-> (String -> Msg)
-> List String
-> List (Html Msg)
-> List (Html Msg)
viewBucket title buckets searchMsgFor selectedBucket sets =
List.append
sets
(if List.isEmpty buckets then
[]
else
[ li []
[ ul []
(List.append
[ li [ class "header" ] [ text title ] ]
(List.map
(\bucket ->
li []
[ a
[ href "#"
, onClick <| searchMsgFor bucket.key
, classList
[ ( "selected"
, List.member bucket.key selectedBucket
)
]
]
[ span [] [ text bucket.key ]
, span [] [ span [ class "badge" ] [ text <| String.fromInt bucket.doc_count ] ]
]
]
)
buckets
)
)
]
]
)
filterPlatformsBucket : List { a | key : String } -> List { a | key : String }
filterPlatformsBucket =
List.filter (\a -> List.member a.key platforms)
viewSuccess :
String
-> Bool
-> Details
-> Maybe String
-> List (Search.ResultItem ResultItemSource)
-> Html Msg
viewSuccess channel showNixOSDetails show hits =
viewSuccess channel showInstallDetails show hits =
ul []
(List.map
(viewResultItem channel showNixOSDetails show)
(viewResultItem channel showInstallDetails show)
hits
)
viewResultItem :
String
-> Bool
-> Details
-> Maybe String
-> Search.ResultItem ResultItemSource
-> Html Msg
viewResultItem channel showNixOSDetails show item =
viewResultItem channel showInstallDetails show item =
let
cleanPosition =
Regex.fromString "^[0-9a-f]+\\.tar\\.gz\\/"
@ -346,23 +323,7 @@ viewResultItem channel showNixOSDetails show item =
shortPackageDetails =
ul []
((item.source.position
|> Maybe.map
(\position ->
case Search.channelDetailsFromId channel of
Nothing ->
[]
Just channelDetails ->
[ li [ trapClick ]
[ createShortDetailsItem
"Source"
(createGithubUrl channelDetails.branch position)
]
]
)
|> Maybe.withDefault []
)
(renderSource item channel trapClick createShortDetailsItem createGithubUrl
|> List.append
(item.source.homepage
|> List.head
@ -420,7 +381,7 @@ viewResultItem channel showNixOSDetails show item =
Nothing ->
"#"
]
[ text <| Maybe.withDefault "" maintainer.name ++ " <" ++ Maybe.withDefault "" maintainer.email ++ ">" ]
[ text <| Maybe.withDefault "" maintainer.name ++ (Maybe.withDefault "" <| Maybe.map (\email -> " <" ++ email ++ ">") maintainer.email) ]
]
showPlatform platform =
@ -479,62 +440,105 @@ viewResultItem channel showNixOSDetails show item =
, em [] [ text item.source.attr_name ]
, text "?"
]
, ul [ class "nav nav-tabs" ]
[ li
[ classList
[ ( "active", showNixOSDetails )
, ( "pull-right", True )
, ul [ class "nav nav-tabs" ] <|
Maybe.withDefault
[ li
[ classList
[ ( "active", List.member showInstallDetails [ Search.Unset, Search.FromNixOS, Search.FromFlake ] )
, ( "pull-right", True )
]
]
[ a
[ href "#"
, Search.onClickStop <|
SearchMsg <|
Search.ShowInstallDetails Search.FromNixOS
]
[ text "On NixOS" ]
]
, li
[ classList
[ ( "active", showInstallDetails == Search.FromNixpkgs )
, ( "pull-right", True )
]
]
[ a
[ href "#"
, Search.onClickStop <|
SearchMsg <|
Search.ShowInstallDetails Search.FromNixpkgs
]
[ text "On non-NixOS" ]
]
]
[ a
[ href "#"
, Search.onClickStop <|
SearchMsg <|
Search.ShowNixOSDetails True
]
[ text "On NixOS" ]
]
, li
[ classList
[ ( "active", not showNixOSDetails )
, ( "pull-right", True )
]
]
[ a
[ href "#"
, Search.onClickStop <|
SearchMsg <|
Search.ShowNixOSDetails False
]
[ text "On non-NixOS" ]
]
]
<|
Maybe.map
(\_ ->
[ li
[ classList
[ ( "active", True )
, ( "pull-right", True )
]
]
[ a
[ href "#"
, Search.onClickStop <|
SearchMsg <|
Search.ShowInstallDetails Search.FromFlake
]
[ text "Install from flake" ]
]
]
)
item.source.flakeUrl
, div
[ class "tab-content" ]
[ div
[ classList
[ ( "active", not showNixOSDetails )
<|
Maybe.withDefault
[ div
[ classList
[ ( "active", showInstallDetails == Search.FromNixpkgs )
]
, class "tab-pane"
, id "package-details-nixpkgs"
]
, class "tab-pane"
, id "package-details-nixpkgs"
]
[ pre [ class "code-block" ]
[ text "nix-env -iA nixpkgs."
, strong [] [ text item.source.attr_name ]
[ pre [ class "code-block" ]
[ text "nix-env -iA nixpkgs."
, strong [] [ text item.source.attr_name ]
]
]
, div
[ classList
[ ( "tab-pane", True )
, ( "active", List.member showInstallDetails [ Search.Unset, Search.FromNixOS, Search.FromFlake ] )
]
]
[ pre [ class "code-block" ]
[ text <| "nix-env -iA nixos."
, strong [] [ text item.source.attr_name ]
]
]
]
, div
[ classList
[ ( "tab-pane", True )
, ( "active", showNixOSDetails )
]
]
[ pre [ class "code-block" ]
[ text <| "nix-env -iA nixos."
, strong [] [ text item.source.attr_name ]
]
]
]
<|
Maybe.map
(\url ->
[ div
[ classList
[ ( "tab-pane", True )
, ( "active", True )
]
]
[ pre [ class "code-block" ]
[ text "nix build "
, strong [] [ text url ]
, text "#"
, em [] [ text item.source.attr_name ]
]
]
]
)
<|
Maybe.map Tuple.first item.source.flakeUrl
]
]
)
@ -551,6 +555,17 @@ viewResultItem channel showNixOSDetails show item =
isOpen =
Just item.source.attr_name == show
flakeItem =
Maybe.map Tuple.second item.source.flakeUrl
|> Maybe.map2
(\name resolved ->
[ li [ trapClick ]
[ createShortDetailsItem name resolved ]
]
)
item.source.flakeName
|> Maybe.withDefault []
in
li
[ class "package"
@ -560,12 +575,18 @@ viewResultItem channel showNixOSDetails show item =
([]
|> List.append longerPackageDetails
|> List.append
[ Html.a
[ class "search-result-button"
, onClick toggle
, href ""
]
[ text item.source.attr_name ]
[ ul [ class "search-result-button" ]
(List.append
flakeItem
[ li []
[ a
[ onClick toggle
, href ""
]
[ text item.source.attr_name ]
]
]
)
, div [] [ text <| Maybe.withDefault "" item.source.description ]
, shortPackageDetails
, Search.showMoreButton toggle isOpen
@ -573,12 +594,50 @@ viewResultItem channel showNixOSDetails show item =
)
renderSource : Search.ResultItem ResultItemSource -> String -> Html.Attribute Msg -> (String -> String -> Html Msg) -> (String -> String -> String) -> List (Html Msg)
renderSource item channel trapClick createShortDetailsItem createGithubUrl =
let
postion =
item.source.position
|> Maybe.map
(\position ->
case Search.channelDetailsFromId channel of
Nothing ->
[]
Just channelDetails ->
[ li [ trapClick ]
[ createShortDetailsItem
"Source"
(createGithubUrl channelDetails.branch position)
]
]
)
flakeDef =
Maybe.map2
(\name resolved ->
[ li [ trapClick ]
[ createShortDetailsItem
("Flake: " ++ name)
resolved
]
]
)
item.source.flakeName
<|
Maybe.map Tuple.second item.source.flakeUrl
in
Maybe.withDefault (Maybe.withDefault [] flakeDef) postion
-- API
makeRequest :
Search.Options
-> SearchType
-> String
-> String
-> Int
@ -586,8 +645,21 @@ makeRequest :
-> Maybe String
-> Search.Sort
-> Cmd Msg
makeRequest options channel query from size maybeBuckets sort =
let
makeRequest options _ channel query from size maybeBuckets sort =
Search.makeRequest
(makeRequestBody query from size maybeBuckets sort)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-packages")
|> Cmd.map SearchMsg
makeRequestBody : String -> Int -> Int -> Maybe String -> Search.Sort -> Body
makeRequestBody query from size maybeBuckets sort =
let
currentBuckets =
initBuckets maybeBuckets
@ -637,36 +709,27 @@ makeRequest options channel query from size maybeBuckets sort =
)
]
in
Search.makeRequest
(Search.makeRequestBody
(String.trim query)
from
size
sort
"package"
"package_attr_name"
[ "package_pversion" ]
[ "package_attr_set"
, "package_license_set"
, "package_maintainers_set"
, "package_platforms"
]
filterByBuckets
"package_attr_name"
[ ( "package_attr_name", 9.0 )
, ( "package_pname", 6.0 )
, ( "package_attr_name_query", 4.0 )
, ( "package_description", 1.3 )
, ( "package_longDescription", 1.0 )
]
)
channel
decodeResultItemSource
decodeResultAggregations
options
Search.QueryResponse
(Just "query-packages")
|> Cmd.map SearchMsg
Search.makeRequestBody
(String.trim query)
from
size
sort
"package"
"package_attr_name"
[ "package_pversion" ]
[ "package_attr_set"
, "package_license_set"
, "package_maintainers_set"
, "package_platforms"
]
filterByBuckets
"package_attr_name"
[ ( "package_attr_name", 9.0 )
, ( "package_pname", 6.0 )
, ( "package_attr_name_query", 4.0 )
, ( "package_description", 1.3 )
, ( "package_longDescription", 1.0 )
]
@ -707,6 +770,56 @@ decodeResultItemSource =
|> Json.Decode.Pipeline.required "package_homepage" decodeHomepage
|> Json.Decode.Pipeline.required "package_system" Json.Decode.string
|> Json.Decode.Pipeline.required "package_hydra" (Json.Decode.nullable (Json.Decode.list decodeResultPackageHydra))
|> Json.Decode.Pipeline.optional "flake_name" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_description" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_resolved" (Json.Decode.map Just decodeResolvedFlake) Nothing
type alias ResolvedFlake =
{ type_ : String, owner : Maybe String, repo : Maybe String, url : Maybe String }
decodeResolvedFlake : Json.Decode.Decoder ( String, String )
decodeResolvedFlake =
let
resolved =
Json.Decode.succeed ResolvedFlake
|> Json.Decode.Pipeline.required "type" Json.Decode.string
|> Json.Decode.Pipeline.optional "owner" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "repo" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "url" (Json.Decode.map Just Json.Decode.string) Nothing
in
Json.Decode.map
(\resolved_ ->
let
repoPath =
case ( resolved_.owner, resolved_.repo ) of
( Just owner, Just repo ) ->
Just <| owner ++ "/" ++ repo
_ ->
Nothing
url =
resolved_.url
result =
case resolved_.type_ of
"github" ->
Maybe.map (\repoPath_ -> ( "github:" ++ repoPath_, "https://github.com/" ++ repoPath_ )) repoPath
"gitlab" ->
Maybe.map (\repoPath_ -> ( "gitlab:" ++ repoPath_, "https://gitlab.com/" ++ repoPath_ )) repoPath
"git" ->
Maybe.map (\url_ -> ( url_, url_ )) url
_ ->
Nothing
in
Maybe.withDefault ( "INVALID FLAKE ORIGIN", "INVALID FLAKE ORIGIN" ) result
)
resolved
filterPlatforms : List String -> List String
@ -743,7 +856,13 @@ decodeResultPackageLicense =
decodeResultPackageMaintainer : Json.Decode.Decoder ResultPackageMaintainer
decodeResultPackageMaintainer =
Json.Decode.map3 ResultPackageMaintainer
(Json.Decode.field "name" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.oneOf
[ Json.Decode.field "name" (Json.Decode.map Just Json.Decode.string)
, Json.Decode.field "email" (Json.Decode.map Just Json.Decode.string)
, Json.Decode.field "github" (Json.Decode.map Just Json.Decode.string)
, Json.Decode.succeed Nothing
]
)
(Json.Decode.field "email" (Json.Decode.nullable Json.Decode.string))
(Json.Decode.field "github" (Json.Decode.nullable Json.Decode.string))

View file

@ -2,13 +2,17 @@ module Route exposing
( Route(..)
, SearchArgs
, SearchRoute
, SearchType(..)
, allTypes
, fromUrl
, href
, replaceUrl
, routeToString
, searchTypeToString, searchTypeToTitle
)
import Browser.Navigation
import Dict
import Html
import Html.Attributes
import Route.SearchQuery exposing (SearchQuery)
@ -32,9 +36,61 @@ type alias SearchArgs =
-- TODO: embed sort type
, sort : Maybe String
, type_ : Maybe SearchType
}
type SearchType
= OptionSearch
| PackageSearch
-- | FlakeSearch
allTypes : List SearchType
allTypes =
[ PackageSearch, OptionSearch ]
searchTypeFromString : String -> Maybe SearchType
searchTypeFromString string =
case string of
"options" ->
Just OptionSearch
"packages" ->
Just PackageSearch
-- "flakes" ->
-- Just FlakeSearch
_ ->
Nothing
searchTypeToString : SearchType -> String
searchTypeToString stype =
case stype of
OptionSearch ->
"options"
PackageSearch ->
"packages"
-- FlakeSearch ->
-- "flakes"
searchTypeToTitle : SearchType -> String
searchTypeToTitle stype =
case stype of
OptionSearch ->
"Options"
PackageSearch ->
"Packages"
-- FlakeSearch ->
-- "flakes"
type alias SearchRoute =
SearchArgs -> Route
@ -56,6 +112,7 @@ searchQueryParser url =
<?> Url.Parser.Query.int "size"
<?> Url.Parser.Query.string "buckets"
<?> Url.Parser.Query.string "sort"
<?> Url.Parser.Query.map (Maybe.andThen searchTypeFromString) (Url.Parser.Query.string "type")
searchArgsToUrl : SearchArgs -> ( List QueryParameter, Maybe ( String, Route.SearchQuery.SearchQuery ) )
@ -67,6 +124,7 @@ searchArgsToUrl args =
, Maybe.map (Url.Builder.int "size") args.size
, Maybe.map (Url.Builder.string "buckets") args.buckets
, Maybe.map (Url.Builder.string "sort") args.sort
, Maybe.map (Url.Builder.string "type") <| Maybe.map searchTypeToString args.type_
]
, Maybe.map (Tuple.pair "query") args.query
)

View file

@ -4,14 +4,19 @@ module Search exposing
, Model
, Msg(..)
, Options
, ResultHits
, ResultItem
, SearchResult
, Sort(..)
, channelDetailsFromId
, channels
, decodeAggregation
, decodeResolvedFlake
, decodeResult
, defaultFlakeId
, elementId
, flakeFromId
, flakes
, fromSortId
, init
, makeRequest
@ -22,6 +27,7 @@ module Search exposing
, trapClick
, update
, view
, viewResult, Details(..)
)
import Base64
@ -65,16 +71,19 @@ import Html.Events
)
import Http
import Json.Decode
import Json.Decode.Pipeline
import Json.Encode
import RemoteData
import Route
import Route exposing (SearchArgs, SearchType)
import Route.SearchQuery
import Set
import Task
import Browser.Events exposing (Visibility(..))
type alias Model a b =
{ channel : String
, flake : String
, query : Maybe String
, result : RemoteData.WebData (SearchResult a b)
, show : Maybe String
@ -83,7 +92,8 @@ type alias Model a b =
, buckets : Maybe String
, sort : Sort
, showSort : Bool
, showNixOSDetails : Bool
, showInstallDetails : Details
, searchType : Route.SearchType
}
@ -135,12 +145,75 @@ type Sort
| AlphabeticallyDesc
type alias ResolvedFlake =
{ type_ : String, owner : Maybe String, repo : Maybe String, url : Maybe String }
decodeResolvedFlake : Json.Decode.Decoder String
decodeResolvedFlake =
let
resolved =
Json.Decode.succeed ResolvedFlake
|> Json.Decode.Pipeline.required "type" Json.Decode.string
|> Json.Decode.Pipeline.optional "owner" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "repo" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "url" (Json.Decode.map Just Json.Decode.string) Nothing
in
Json.Decode.map
(\resolved_ ->
let
repoPath =
case ( resolved_.owner, resolved_.repo ) of
( Just owner, Just repo ) ->
Just <| owner ++ "/" ++ repo
_ ->
Nothing
url =
resolved_.url
result =
case resolved_.type_ of
"github" ->
Maybe.map (\repoPath_ -> "https://github.com/" ++ repoPath_) repoPath
"gitlab" ->
Maybe.map (\repoPath_ -> "https://gitlab.com/" ++ repoPath_) repoPath
"git" ->
url
_ ->
Nothing
in
Maybe.withDefault "INVALID FLAKE ORIGIN" result
)
resolved
init :
Route.SearchArgs
-> Maybe (Model a b)
-> ( Model a b, Cmd (Msg a b) )
init args maybeModel =
let
emptyRoute : Route.SearchArgs
emptyRoute =
{ query = Nothing
, channel = Nothing
, show = Nothing
, from = Nothing
, size = Nothing
, buckets = Nothing
-- TODO= Nothing type
, sort = Nothing
, type_ = Nothing
}
-- args =
-- Maybe.withDefault emptyRoute maybeArgs
getField getFn default =
maybeModel
|> Maybe.map getFn
@ -158,6 +231,7 @@ init args maybeModel =
( { channel =
args.channel
|> Maybe.withDefault modelChannel
, flake = defaultFlakeId
, query =
args.query
|> Maybe.andThen Route.SearchQuery.searchQueryToString
@ -176,7 +250,8 @@ init args maybeModel =
|> fromSortId
|> Maybe.withDefault Relevance
, showSort = False
, showNixOSDetails = False
, showInstallDetails = Unset
, searchType = Maybe.withDefault Route.PackageSearch args.type_
}
|> ensureLoading
, Browser.Dom.focus "search-query-input" |> Task.attempt (\_ -> NoOp)
@ -194,7 +269,7 @@ ensureLoading :
Model a b
-> Model a b
ensureLoading model =
if model.query /= Nothing && model.query /= Just "" && List.member model.channel channels then
if model.query /= Nothing && model.query /= Just "" && (List.member model.channel channels || List.member model.channel flakeIds) then
{ model | result = RemoteData.Loading }
else
@ -218,14 +293,20 @@ type Msg a b
| ToggleSort
| BucketsChange String
| ChannelChange String
| FlakeChange String
| SubjectChange SearchType
| QueryInput String
| QueryInputSubmit
| QueryResponse (RemoteData.WebData (SearchResult a b))
| ShowDetails String
| ChangePage Int
| ShowNixOSDetails Bool
| ShowInstallDetails Details
type Details
= FromNixpkgs
| FromNixOS
| FromFlake
| Unset
scrollToEntry :
Maybe String
-> Cmd (Msg a b)
@ -292,6 +373,26 @@ update toRoute navKey msg model =
|> ensureLoading
|> pushUrl toRoute navKey
FlakeChange flake ->
{ model
| channel = flake
, show = Nothing
, buckets = Nothing
, from = 0
}
|> ensureLoading
|> pushUrl toRoute navKey
SubjectChange subject ->
{ model
| searchType = subject
, show = Nothing
, buckets = Nothing
, from = 0
}
|> ensureLoading
|> pushUrl toRoute navKey
QueryInput query ->
( { model | query = Just query }
, Cmd.none
@ -307,6 +408,10 @@ update toRoute navKey msg model =
|> pushUrl toRoute navKey
QueryResponse result ->
-- let
-- _ =
-- Debug.log "got query result" result
-- in
( { model
| result = result
}
@ -329,8 +434,8 @@ update toRoute navKey msg model =
|> ensureLoading
|> pushUrl toRoute navKey
ShowNixOSDetails show ->
{ model | showNixOSDetails = show }
ShowInstallDetails details ->
{ model | showInstallDetails = details }
|> pushUrl toRoute navKey
@ -362,6 +467,7 @@ createUrl toRoute model =
, size = Just model.size
, buckets = model.buckets
, sort = Just <| toSortId model.sort
, type_ = Just model.searchType
}
@ -395,13 +501,14 @@ channelDetails : Channel -> ChannelDetails
channelDetails channel =
case channel of
Unstable ->
ChannelDetails "unstable" "unstable" "nixos/trunk-combined" "nixpkgs-unstable"
ChannelDetails "unstable" "unstable" "nixos/trunk-combined" "nixos-unstable"
Release_20_09 ->
ChannelDetails "20.09" "20.09" "nixos/release-20.09" "nixpkgs-20.09"
ChannelDetails "20.09" "20.09" "nixos/release-20.09" "nixos-20.09"
Release_21_05 ->
ChannelDetails "21.05" "21.05" "nixos/release-21.05" "nixpkgs-21.05"
ChannelDetails "21.05" "21.05" "nixos/release-21.05" "nixos-21.05"
channelFromId : String -> Maybe Channel
channelFromId channel_id =
@ -433,6 +540,68 @@ channels =
]
type alias Flake =
{ id : String
, isNixpkgs : Bool
, title : String
, source : String
}
defaultFlakeId : String
defaultFlakeId =
"group-01"
flakeFromId : String -> Maybe Flake
flakeFromId flake_id =
let
find : String -> List Flake -> Maybe Flake
find id_ list =
case list of
flake :: rest ->
if flake.id == id_ then
Just flake
else
find id_ rest
[] ->
Nothing
in
find flake_id flakes
flakeIds : List String
flakeIds =
List.map .id flakes
flakes : List Flake
flakes =
[ { id = "latest-nixos-20.09-latest"
, isNixpkgs = True
, title = "Nixpkgs 20.09"
, source = ""
}
, { id = "nixos-21.05-latest"
, isNixpkgs = True
, title = "Nixpkgs 21.05"
, source = ""
}
, { id = "latest-nixos-unstable"
, isNixpkgs = True
, title = "Nixpkgs Unstable"
, source = ""
}
, { id = "flakes"
, isNixpkgs = False
, title = "Public Flakes"
, source = ""
}
]
sortBy : List Sort
sortBy =
[ Relevance
@ -579,7 +748,7 @@ view :
-> Model a b
->
(String
-> Bool
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
@ -590,8 +759,9 @@ view :
-> List (Html c)
)
-> (Msg a b -> c)
-> List (Html c)
-> Html c
view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg =
view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg searchBuckets =
let
resultStatus =
case model.result of
@ -619,7 +789,7 @@ view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg =
)
[ h1 [] title
, viewSearchInput outMsg categoryName model.channel model.query
, viewResult outMsg toRoute categoryName model viewSuccess viewBuckets
, viewResult outMsg toRoute categoryName model viewSuccess viewBuckets searchBuckets
]
@ -630,7 +800,7 @@ viewResult :
-> Model a b
->
(String
-> Bool
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
@ -640,15 +810,17 @@ viewResult :
-> SearchResult a b
-> List (Html c)
)
-> List (Html c)
-> Html c
viewResult outMsg toRoute categoryName model viewSuccess viewBuckets =
viewResult outMsg toRoute categoryName model viewSuccess viewBuckets searchBuckets =
case model.result of
RemoteData.NotAsked ->
div [] [ text "" ]
div [] [ ]
RemoteData.Loading ->
div [ class "loader-wrapper" ]
[ div [ class "loader" ] [ text "Loading..." ]
[ ul [ class "search-sidebar" ] searchBuckets
, div [ class "loader" ] [ text "Loading..." ]
, h2 [] [ text "Searching..." ]
]
@ -658,18 +830,22 @@ viewResult outMsg toRoute categoryName model viewSuccess viewBuckets =
viewBuckets model.buckets result
in
if result.hits.total.value == 0 && List.length buckets == 0 then
viewNoResults categoryName
div [ class "search-results" ]
[ ul [ class "search-sidebar" ] searchBuckets
, viewNoResults categoryName
]
else if List.length buckets > 0 then
div [ class "search-results" ]
[ ul [] buckets
[ ul [ class "search-sidebar" ] <| List.append searchBuckets buckets
, div []
(viewResults model result viewSuccess toRoute outMsg categoryName)
]
else
div [ class "search-results" ]
[ div []
[ ul [ class "search-sidebar" ] searchBuckets
, div []
(viewResults model result viewSuccess toRoute outMsg categoryName)
]
@ -694,7 +870,8 @@ viewResult outMsg toRoute categoryName model viewSuccess viewBuckets =
in
div []
[ div [ class "alert alert-error" ]
[ h4 [] [ text errorTitle ]
[ ul [ class "search-sidebar" ] searchBuckets
, h4 [] [ text errorTitle ]
, text errorMessage
]
]
@ -793,7 +970,7 @@ viewResults :
-> SearchResult a b
->
(String
-> Bool
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
@ -845,7 +1022,7 @@ viewResults model result viewSuccess toRoute outMsg categoryName =
)
)
]
, viewSuccess model.channel model.showNixOSDetails model.show result.hits.hits
, viewSuccess model.channel model.showInstallDetails model.show result.hits.hits
, Html.map outMsg <| viewPager model result.hits.total.value
]
@ -1139,7 +1316,7 @@ makeRequest :
-> Maybe String
-> Cmd (Msg a b)
makeRequest body channel decodeResultItemSource decodeResultAggregations options responseMsg tracker =
let branch = Maybe.map (\details -> details.branch) (channelDetailsFromId channel) |> Maybe.withDefault ""
let branch = Maybe.map (\details -> details.branch) (channelDetailsFromId channel) |> Maybe.withDefault channel
index = "latest-" ++ String.fromInt options.mappingSchemaVersion ++ "-" ++ branch
in
Http.riskyRequest

29
src/View/Components.elm Normal file
View file

@ -0,0 +1,29 @@
module View.Components exposing (..)
import Html exposing (Html)
import Route exposing (SearchRoute)
import Search exposing (Model, Msg, ResultItem, SearchResult)
import View.Components.Body
import Search exposing (Details)
body :
{ toRoute : SearchRoute, categoryName : String }
-> List (Html c)
-> Model a b
->
(String
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
)
->
(Maybe String
-> SearchResult a b
-> List (Html c)
)
-> (Msg a b -> c)
-> Html c
body =
View.Components.Body.view

View file

@ -0,0 +1,65 @@
module View.Components.Body exposing (..)
import Html exposing (Html, div, h1)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)
import RemoteData exposing (RemoteData(..))
import Route
import Search exposing (Model, Msg(..), ResultItem, SearchResult, viewResult)
import View.Components.SearchInput exposing (viewSearchInput)
import View.Components.SearchInput exposing (viewFlakes)
import Html exposing (details)
import Search exposing (Details)
view :
{ toRoute : Route.SearchRoute
, categoryName : String
}
-> List (Html c)
-> Model a b
->
(String
-> Details
-> Maybe String
-> List (ResultItem a)
-> Html c
)
->
(Maybe String
-> SearchResult a b
-> List (Html c)
)
-> (Msg a b -> c)
-> Html c
view { toRoute, categoryName } title model viewSuccess viewBuckets outMsg =
let
resultStatus =
case model.result of
RemoteData.NotAsked ->
"not-asked"
RemoteData.Loading ->
"loading"
RemoteData.Success _ ->
"success"
RemoteData.Failure _ ->
"failure"
in
div
(List.append
[ class <| "search-page " ++ resultStatus ]
(if model.showSort then
[ onClick (outMsg ToggleSort) ]
else
[]
)
)
[ h1 [] title
, viewSearchInput outMsg model.searchType model.query
, viewResult outMsg toRoute categoryName model viewSuccess viewBuckets <| viewFlakes outMsg model.channel model.searchType
]

View file

@ -0,0 +1,118 @@
module View.Components.SearchInput exposing (..)
import Html exposing (Html, a, button, div, form, h4, input, li, p, span, text, th, ul)
import Html.Attributes exposing (attribute, autofocus, class, classList, href, id, placeholder, selected, type_, value)
import Html.Events exposing (onClick, onInput, onSubmit)
import Route exposing (SearchType, allTypes, searchTypeToString, searchTypeToTitle)
import Search exposing (Msg(..))
viewSearchInput :
(Msg a b -> c)
-> SearchType
-> Maybe String
-> Html c
viewSearchInput outMsg category searchQuery =
let
searchHint =
Maybe.withDefault "Packages and Options" <| Maybe.map (\_ -> searchTypeToString category) searchQuery
in
form
[ onSubmit (outMsg QueryInputSubmit)
, class "search-input"
]
[ div []
[ div []
[ input
[ type_ "text"
, id "search-query-input"
, autofocus True
, placeholder <| "Search for " ++ searchHint
, onInput (outMsg << QueryInput)
, value <| Maybe.withDefault "" searchQuery
]
[]
]
, button [ class "btn", type_ "submit" ]
[ text "Search" ]
]
]
viewFlakes : (Msg a b -> msg) -> String -> SearchType -> List (Html msg)
viewFlakes outMsg selectedFlake selectedCategory =
[ li []
[ ul []
(List.map
(\category ->
li []
[ a
[ href "#"
, onClick <| outMsg (SubjectChange category)
, classList
[ ( "selected"
, category == selectedCategory
)
]
]
[ span [] [ text <| searchTypeToTitle category ]
, closeButton
]
]
)
allTypes
)
]
]
closeButton : Html a
closeButton =
span [] []
viewBucket :
String
-> List Search.AggregationsBucketItem
-> (String -> a)
-> List String
-> List (Html a)
-> List (Html a)
viewBucket title buckets searchMsgFor selectedBucket sets =
List.append
sets
(if List.isEmpty buckets then
[]
else
[ li []
[ ul []
(List.append
[ li [ class "header" ] [ text title ] ]
(List.map
(\bucket ->
li []
[ a
[ href "#"
, onClick <| searchMsgFor bucket.key
, classList
[ ( "selected"
, List.member bucket.key selectedBucket
)
]
]
[ span [] [ text bucket.key ]
, if List.member bucket.key selectedBucket then
closeButton
else
span [] [ span [ class "badge" ] [ text <| String.fromInt bucket.doc_count ] ]
]
]
)
buckets
)
)
]
]
)

View file

@ -86,6 +86,9 @@ header .navbar.navbar-static-top {
}
ul.nav > li {
line-height: 20px;
sup {
margin-left: 0.5em;
}
}
}
@ -173,17 +176,28 @@ header .navbar.navbar-static-top {
.search-result-button {
&:hover {
text-decoration: underline;
list-style: none;
margin: 0;
padding: 0;
& > li {
display: inline-block;
&:nth-child(1):after {
content: "→";
margin: 0 0.2em;
}
& > a:hover {
text-decoration: underline;
}
}
}
& > .search-results {
display: flex;
flex-direction: row;
// Buckets
ul.search-sidebar {
width: 25em;
// Buckets
& > ul {
list-style: none;
margin: 0 1em 0 0;
@ -208,7 +222,7 @@ header .navbar.navbar-static-top {
& > a {
display: grid;
grid-template-columns: auto auto;
grid-template-columns: auto max-content;
color: #333;
padding: 0.5em 0.5em 0.5em 1em;
text-decoration: none;
@ -232,24 +246,19 @@ header .navbar.navbar-static-top {
color: #FFF;
border-radius: 4px;
position: relative;
&:after {
content: "\2715";
font-size: 1.7em;
color: #fff;
position: absolute;
top: 4px;
right: 4px;
background: #0081c2;
bottom: 4px;
width: 1em;
padding: 2px;
}
& > span:last-child {
& > span:last-child {
display: none;
}
}
}
& .close {
opacity: 1;
text-shadow: none;
color: inherit;
font-size: inherit;
padding-left: .5em;
padding-right: .5em;
}
}
}
@ -257,6 +266,11 @@ header .navbar.navbar-static-top {
}
}
& > .search-results {
display: flex;
flex-direction: row;
// Results section
& > div {
width: 100%;