Compare commits
53 Commits
em_issue-1
...
main
Author | SHA1 | Date |
---|---|---|
3wordchant | bbf8b3baa5 | |
3wc | a5230dfde3 | |
3wc | 9ab689f69e | |
3wc | e566a6eef4 | |
Comrade Renovate Bot | 209cdc2770 | |
Comrade Renovate Bot | 2482caf2e0 | |
decentral1se | d15df0f9e4 | |
Comrade Renovate Bot | 91f8340ac8 | |
3wc | 6851d2fe1f | |
3wc | e2f9cdd7e2 | |
3wc | 6ed6cbb33e | |
3wordchant | 41ccf212a9 | |
3wc | 382d36d001 | |
3wc | 2dec76fbfd | |
3wc | 377c8a7d04 | |
3wc | f7ffc49b76 | |
3wc | 338da444d8 | |
3wc | 57b4db9bb2 | |
3wc | 70307aad9e | |
3wordchant | af5565dcc5 | |
3wc | 4a375f3d5f | |
3wc | d8b2192240 | |
3wc | 65ef1d2772 | |
KawaiiPunk | c50635319b | |
3wc | 32366b7bf7 | |
3wc | dbb701a2a4 | |
3wc | b761f39247 | |
3wc | a7435c3d37 | |
3wc | 429cb371a8 | |
3wc | 774c59bfa8 | |
3wc | 39c3122230 | |
3wc | a7ea4207ce | |
Comrade Renovate Bot | 6769455c27 | |
Comrade Renovate Bot | 984c205299 | |
decentral1se | 5b28441102 | |
Comrade Renovate Bot | d278ec2ad4 | |
3wc | 3f10abe1c5 | |
3wc | 1a7ade0f76 | |
3wc | 0777f7c008 | |
3wc | 69a7fbc036 | |
3wc | 4c5e151615 | |
3wc | 9548d0715e | |
3wc | 68669b7445 | |
decentral1se | 1c306e8607 | |
decentral1se | 375cafb946 | |
decentral1se | 910d0bf2ae | |
3wc | 31f8a0be69 | |
3wc | 9c4522dceb | |
decentral1se | 83f0919cd8 | |
Eiven Mitchell | d58ecbfd3c | |
Eiven Mitchell | 3b57477e62 | |
Eiven Mitchell | 72d1822cf0 | |
3wordchant | f10a572d6f |
11
.drone.yml
11
.drone.yml
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: deploy to dev.apps.coopcloud.tech
|
||||
name: deploy to recipes.coopcloud.tech
|
||||
steps:
|
||||
- name: build image
|
||||
image: plugins/docker
|
||||
|
@ -13,12 +13,13 @@ steps:
|
|||
tags: latest
|
||||
|
||||
- name: deployment
|
||||
image: decentral1se/stack-ssh-deploy:latest
|
||||
image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest
|
||||
settings:
|
||||
stack: dev_apps_coopcloud_tech
|
||||
stack: recipes_coopcloud_tech
|
||||
host: swarm-0.coopcloud.tech
|
||||
deploy_key:
|
||||
from_secret: drone_ssh_swarm-demo.autonomic.zone
|
||||
host: swarm-demo.autonomic.zone
|
||||
from_secret: drone_ssh_swarm-0_coopcloud_tech
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# authors
|
||||
|
||||
> If you're looking at this and you hack on `abra` and you're not listed here,
|
||||
> please do add yourself! This is a community project, let's show some :heart:
|
||||
|
||||
- 3wordchant
|
||||
- eejum
|
||||
- hazelnot
|
11
Dockerfile
11
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
FROM node:12-alpine
|
||||
FROM node:21-alpine AS build
|
||||
|
||||
RUN mkdir /code
|
||||
WORKDIR /code
|
||||
|
@ -11,5 +11,10 @@ ENV PATH=$PATH:/code/node_modules/elm-linter/bin:/code/node_modules/elm-format/b
|
|||
# Add remainder of files
|
||||
COPY . .
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/npm"]
|
||||
CMD ["run", "prod"]
|
||||
RUN ["npm", "run", "build"]
|
||||
|
||||
FROM nginx
|
||||
|
||||
COPY --from=build /code/public/ /usr/share/nginx/html/
|
||||
|
||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# recipes.coopcloud.tech
|
||||
|
||||
> WIP! https://dev.apps.coopcloud.tech is the current live site
|
||||
> https://recipes.coopcloud.tech is the current live site
|
||||
|
||||
[![Build Status](https://drone.autonomic.zone/api/badges/coop-cloud/recipes.coopcloud.tech/status.svg?ref=refs/heads/main)](https://drone.autonomic.zone/coop-cloud/recipes.coopcloud.tech)
|
||||
[![Build Status](https://build.coopcloud.tech/api/badges/coop-cloud/recipes.coopcloud.tech/status.svg?ref=refs/heads/main)](https://build.coopcloud.tech/coop-cloud/recipes.coopcloud.tech)
|
||||
|
||||
The Co-op Cloud recipes catalogue. Our digital configuration commons, browsable in a simple and pleasant web UI.
|
||||
|
||||
|
@ -33,6 +33,8 @@ __Build command:__ `npm run build`
|
|||
|
||||
__Publish directory:__ `public`
|
||||
|
||||
Then, run `make` to deploy the new version to `mellor.coopcloud.tech`.
|
||||
|
||||
## thanks
|
||||
|
||||
created using [`elm-spa`](https://elm-spa.dev)
|
||||
|
|
23
compose.yml
23
compose.yml
|
@ -6,22 +6,25 @@ services:
|
|||
image: "3wordchant/abra-apps:latest"
|
||||
networks:
|
||||
- proxy
|
||||
healthcheck:
|
||||
test: "nodejs -e \"http.get('http://localhost:8000', (res) => { console.log('status: ', res.statusCode); if (res.statusCode == 200) { process.exit(0); } else { process.exit(1); } });\""
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 1m
|
||||
# healthcheck:
|
||||
# test: "nodejs -e \"http.get('http://localhost:8000', (res) => { console.log('status: ', res.statusCode); if (res.statusCode == 200) { process.exit(0); } else { process.exit(1); } });\""
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 3
|
||||
# start_period: 1m
|
||||
deploy:
|
||||
update_config:
|
||||
failure_action: rollback
|
||||
order: start-first
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.abra-apps-dev.loadbalancer.server.port=8000"
|
||||
- "traefik.http.routers.abra-apps-dev.rule=Host(`dev.apps.coopcloud.tech`)"
|
||||
- "traefik.http.routers.abra-apps-dev.entrypoints=web-secure"
|
||||
- "traefik.http.routers.abra-apps-dev.tls.certresolver=production"
|
||||
- "traefik.http.services.abra-recipes.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.abra-recipes.rule=(Host(`dev.apps.coopcloud.tech`)||Host(`recipes.coopcloud.tech`))"
|
||||
- "traefik.http.routers.abra-recipes.entrypoints=web-secure"
|
||||
- "traefik.http.routers.abra-recipes.tls.certresolver=production"
|
||||
- "traefik.http.routers.abra-recipes.middlewares=abra-recipes-redirect"
|
||||
- "traefik.http.middlewares.abra-recipes-redirect.headers.SSLForceHost=true"
|
||||
- "traefik.http.middlewares.abra-recipes-redirect.headers.SSLHost=recipes.coopcloud.tech"
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
|
|
1
elm.json
1
elm.json
|
@ -16,6 +16,7 @@
|
|||
"elm/url": "1.0.0",
|
||||
"elm-community/json-extra": "4.3.0",
|
||||
"elm-community/string-extra": "4.0.1",
|
||||
"fapian/elm-html-aria": "1.4.0",
|
||||
"pablohirafuji/elm-markdown": "2.0.5"
|
||||
},
|
||||
"indirect": {
|
||||
|
|
4
makefile
4
makefile
|
@ -1,6 +1,6 @@
|
|||
|
||||
deploy:
|
||||
@DOCKER_CONTEXT=mellor.coopcloud.tech \
|
||||
docker stack rm dev_apps_coopcloud_tech && \
|
||||
docker stack rm recipes_coopcloud_tech && \
|
||||
DOCKER_CONTEXT=mellor.coopcloud.tech \
|
||||
docker stack deploy -c compose.yml dev_apps_coopcloud_tech
|
||||
docker stack deploy -c compose.yml recipes_coopcloud_tech
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -22,14 +22,14 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "2.1.0",
|
||||
"elm": "0.19.1-3",
|
||||
"chokidar-cli": "3.0.0",
|
||||
"elm": "0.19.1-6",
|
||||
"elm-live": "4.0.2",
|
||||
"elm-spa": "5.0.4",
|
||||
"elm-test": "0.19.1-revision2",
|
||||
"npm-run-all": "4.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"elm-format": "^0.8.5"
|
||||
"elm-format": "^0.8.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- CSS goes here -->
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<title>abra recipes</title>
|
||||
<title>Co-op Cloud Recipes</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<!--<link rel="stylesheet"
|
||||
|
@ -20,7 +20,7 @@
|
|||
<nav class="navbar navbar-expand-lg sticky-top font-weight-bold" style="background: rgb(255, 79, 136) none repeat scroll 0% 0%;">
|
||||
<a class="navbar-brand text-dark" href="/">
|
||||
<img src="/logo.png" class="d-inline-block align-top mr-2" alt="" width="30" height="30">
|
||||
abra recipes</a>
|
||||
Co-op Cloud Recipes</a>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="pt-3">
|
||||
|
@ -28,11 +28,11 @@
|
|||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
abra recipes</h2>
|
||||
Co-op Cloud Recipes</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
This web application sadly requires Javascript to run.
|
||||
This web application sadly requires Javascript to run.
|
||||
</p>
|
||||
<p>
|
||||
You can
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 214 B |
155
public/style.css
155
public/style.css
|
@ -1,27 +1,153 @@
|
|||
:root {
|
||||
--light-blue: #6A9CFF;
|
||||
--dark-blue: #000099;
|
||||
--light-pink: #FF4F88;
|
||||
--dark-pink: #7E5E69;
|
||||
|
||||
--white: #FFFFFF;
|
||||
--black: #000000;
|
||||
}
|
||||
|
||||
body {
|
||||
}
|
||||
|
||||
.app-headings {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.background {
|
||||
background-color: none;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
min-height: 92vh;
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
width: 100%;
|
||||
height: 16vw;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card.filter {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
i.fas, i.fab {
|
||||
display: inline-block;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
/* Filtering */
|
||||
|
||||
#filter {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
background-color: var(--light-blue);
|
||||
padding-top: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#filter > form > div {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
#filter h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
a.help {
|
||||
display: inline-block;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 14px;
|
||||
color: var(--dark-pink);
|
||||
}
|
||||
|
||||
.category-tile {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-tile:hover {
|
||||
color: var(--dark-blue);
|
||||
}
|
||||
|
||||
.category-tile.active {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/* Intro */
|
||||
|
||||
.card#intro {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
|
||||
.app-category {
|
||||
background-color: var(--light-blue);
|
||||
color: var(--white);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
width: 95%;
|
||||
padding: 0em;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card.filter {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: none;
|
||||
border: none;
|
||||
padding: 0.1em 0.75em;
|
||||
text-align: center;
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
height: 10%;
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
width: 100%;
|
||||
height: 5vw;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-img-large {
|
||||
height: 10vw;
|
||||
}
|
||||
|
||||
.smaller-card {
|
||||
border: 1px solid var(--dark-pink);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.recommended-card {
|
||||
}
|
||||
|
||||
.title-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--dark-blue);
|
||||
}
|
||||
|
||||
|
||||
/* Navbar */
|
||||
.navbar-text {
|
||||
color: var(--dark-blue);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
margin: auto;
|
||||
background-color: var(--light-pink);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.card-body p {
|
||||
.smaller-card .card-body p {
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
@ -30,3 +156,10 @@ i.fas, i.fab {
|
|||
.card-body p {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#filter {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
|
@ -33,37 +33,40 @@ type alias Params =
|
|||
|
||||
view : Url Params -> Document Msg
|
||||
view { params } =
|
||||
let
|
||||
let
|
||||
elm_link = a [ href "https://elm-lang.org/" ] [ text "Elm" ]
|
||||
coopcloud_link = a [ href "https://coopcloud.tech/" ] [ text "Co-op Cloud" ]
|
||||
source_link = a [ href "https://git.coopcloud.tech/coop-cloud/abra-apps" ] [ text "source" ]
|
||||
autonomic_link = a [ href "https://autonomic.zone" ] [ text "Autonomic" ]
|
||||
in
|
||||
{ title = "about – abra recipes"
|
||||
, body =
|
||||
{ title = "About – Co-op Cloud Recipes"
|
||||
, body =
|
||||
[ div [ class "pt-3" ]
|
||||
[ div [ class "col-md-6 col-sm-10 mb-3 offset-md-3 offset-sm-1" ]
|
||||
[ div [ class "col-md-6 col-sm-10 mb-3 offset-md-3 offset-sm-1" ]
|
||||
[ div [ class "card" ]
|
||||
[ div [ class "card-header" ]
|
||||
[ h2 [] [ text "abra recipes" ]
|
||||
[ h2 [] [ text "Co-op Cloud Recipes" ]
|
||||
]
|
||||
, div [ class "card-body" ]
|
||||
[ p []
|
||||
[ p []
|
||||
[
|
||||
text "a lil' "
|
||||
, text " tool to display "
|
||||
, coopcloud_link
|
||||
, text " apps."
|
||||
, text " recipes."
|
||||
]
|
||||
, p []
|
||||
[
|
||||
text "written in "
|
||||
, elm_link
|
||||
, elm_link
|
||||
, text " for some reason 🤷"
|
||||
]
|
||||
]
|
||||
, div [ class "card-footer" ]
|
||||
[ p []
|
||||
[ text "by @3wc ("
|
||||
[ p []
|
||||
[ text "made with 🤪 at "
|
||||
, autonomic_link
|
||||
, text " ("
|
||||
, source_link
|
||||
, text ")"
|
||||
]
|
||||
|
|
|
@ -37,9 +37,9 @@ type alias App =
|
|||
{ name : String
|
||||
, category : String
|
||||
, repository : Maybe String
|
||||
, versions : Maybe (List String)
|
||||
, versions : List String
|
||||
, icon : Maybe String
|
||||
, status : String
|
||||
, status : Int
|
||||
, slug : String
|
||||
, default_branch : String
|
||||
, website : Maybe String
|
||||
|
@ -151,13 +151,13 @@ title : Model -> String
|
|||
title model =
|
||||
case model.status of
|
||||
Loading ->
|
||||
"loading – abra recipes"
|
||||
"loading – Co-op Cloud Recipes"
|
||||
|
||||
Failure ->
|
||||
"error –- abra recipes"
|
||||
"error –- Co-op Cloud Recipes"
|
||||
|
||||
Success app ->
|
||||
app.name ++ " – abra recipes"
|
||||
app.name ++ " – Co-op Cloud Recipes"
|
||||
|
||||
|
||||
body : Model -> Html Msg
|
||||
|
@ -192,23 +192,23 @@ viewStatusBadge app =
|
|||
let
|
||||
status_class =
|
||||
case app.status of
|
||||
"1" ->
|
||||
5 ->
|
||||
"badge-success"
|
||||
|
||||
"2" ->
|
||||
4 ->
|
||||
"badge-info"
|
||||
|
||||
"3" ->
|
||||
3 ->
|
||||
"badge-warning"
|
||||
|
||||
"4" ->
|
||||
2 ->
|
||||
"badge-danger"
|
||||
|
||||
_ ->
|
||||
"badge-dark"
|
||||
in
|
||||
span [ class ("card-link badge " ++ status_class) ]
|
||||
[ text ("Score: " ++ app.status) ]
|
||||
[ text ("Score: " ++ String.fromInt app.status) ]
|
||||
|
||||
|
||||
viewApp : App -> String -> Html Msg
|
||||
|
@ -265,13 +265,16 @@ viewApp app readme =
|
|||
, repository_link
|
||||
, website_link
|
||||
]
|
||||
, img [ class "card-img-top", src icon_url, alt ("icon for " ++ app.name) ] []
|
||||
, img [ class "card-img-top card-img-large", src icon_url, alt ("icon for " ++ app.name) ] []
|
||||
, div [ class "card-body" ]
|
||||
-- render Markdown with no special options
|
||||
[ div [] (Markdown.toHtml Nothing readme)
|
||||
]
|
||||
, div [ class "card-footer" ]
|
||||
[]
|
||||
[ h5 [] [ text "Versions" ]
|
||||
, ul []
|
||||
(List.map (\version -> li [] [ text version ]) app.versions)
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -283,7 +286,7 @@ viewApp app readme =
|
|||
loadApp : Cmd Msg
|
||||
loadApp =
|
||||
Http.get
|
||||
{ url = "https://apps.coopcloud.tech/"
|
||||
{ url = "https://recipes.coopcloud.tech/recipes.json"
|
||||
, expect = Http.expectJson GotApps appListDecoder
|
||||
}
|
||||
|
||||
|
@ -304,27 +307,37 @@ loadREADME app =
|
|||
in
|
||||
Http.get
|
||||
-- FIXME use live Gitea link
|
||||
{ url = "https://cors-container.herokuapp.com/https://git.coopcloud.tech/coop-cloud/" ++ app.slug ++ "/raw/branch/" ++ app.default_branch ++ "/README.md"
|
||||
{ url = "https://git.coopcloud.tech/coop-cloud/" ++ app.slug ++ "/raw/branch/" ++ app.default_branch ++ "/README.md"
|
||||
, expect = Http.expectString GotText
|
||||
}
|
||||
|
||||
|
||||
featuresDecoder : Decode.Decoder String
|
||||
featuresDecoder =
|
||||
-- get features.status if it's there
|
||||
Decode.oneOf
|
||||
[ Decode.at [ "status" ] Decode.string
|
||||
, Decode.succeed ""
|
||||
[ Decode.at [ "status" ] Decode.int
|
||||
, Decode.succeed 5
|
||||
]
|
||||
|
||||
|
||||
versionsDecoder : Decode.Decoder (List String)
|
||||
versionsDecoder =
|
||||
Decode.list (Decode.keyValuePairs Decode.value)
|
||||
|> Decode.map buildVersions
|
||||
|
||||
|
||||
buildVersions : List (List ( String, Decode.Value )) -> List String
|
||||
buildVersions versions =
|
||||
List.concatMap (List.map (\( version, _ ) -> version)) versions
|
||||
|
||||
|
||||
appDecoder : Decode.Decoder App
|
||||
appDecoder =
|
||||
Decode.succeed App
|
||||
|> andMap (Decode.field "name" Decode.string)
|
||||
|> andMap (Decode.field "category" Decode.string)
|
||||
|> andMap (Decode.maybe (Decode.field "repository" Decode.string))
|
||||
|> andMap (Decode.succeed Nothing)
|
||||
|> andMap (Decode.at [ "versions" ] versionsDecoder)
|
||||
-- |> andMap (Decode.succeed Nothing)
|
||||
|> andMap (Decode.maybe (Decode.field "icon" Decode.string))
|
||||
|> andMap (Decode.at [ "features" ] featuresDecoder)
|
||||
|> andMap (Decode.succeed "")
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
module Pages.Top exposing (Model, Msg, Params, page)
|
||||
|
||||
import Enum exposing (Enum)
|
||||
import Html exposing (Html, a, button, div, h2, h5, i, img, li, p, span, text, ul, form, label, select, option, input)
|
||||
import Html.Attributes exposing (alt, class, href, src, style, id, for, value)
|
||||
import Html exposing (Html, a, button, div, form, h2, h3, h5, i, img, input, label, li, option, p, select, span, text, ul)
|
||||
import Html.Attributes exposing (alt, class, classList, for, href, id, src, style, target, type_, value)
|
||||
import Html.Attributes.Aria exposing (ariaLabel)
|
||||
import Html.Events exposing (onClick, onInput)
|
||||
import Http
|
||||
import Json.Decode as Decode
|
||||
|
@ -26,6 +27,7 @@ page =
|
|||
}
|
||||
|
||||
|
||||
|
||||
-- INIT
|
||||
|
||||
|
||||
|
@ -52,8 +54,8 @@ type alias Model =
|
|||
, filter_category : Maybe Category
|
||||
, filter_text : Maybe String
|
||||
, status : Status
|
||||
, apps : (List App)
|
||||
, results : (List App)
|
||||
, apps : List App
|
||||
, results : List App
|
||||
}
|
||||
|
||||
|
||||
|
@ -68,18 +70,19 @@ type Category
|
|||
| Utilities
|
||||
| Development
|
||||
| Graveyard
|
||||
| All
|
||||
|
||||
|
||||
categories : Enum Category
|
||||
categories =
|
||||
Enum.create
|
||||
[ ( "Apps", Apps )
|
||||
[ ( "(Everything)", All )
|
||||
, ( "Apps", Apps )
|
||||
, ( "Utilities", Utilities )
|
||||
, ( "Development", Development )
|
||||
]
|
||||
|
||||
|
||||
|
||||
init : Url Params -> ( Model, Cmd Msg )
|
||||
init { params } =
|
||||
( default_model, loadApps )
|
||||
|
@ -92,8 +95,14 @@ default_image =
|
|||
|
||||
default_model : Model
|
||||
default_model =
|
||||
{ filter_score = Nothing, filter_category = Nothing, filter_text = Nothing,
|
||||
status = Loading, apps = [], results = [] }
|
||||
{ filter_score = Nothing
|
||||
, filter_category = Nothing
|
||||
, filter_text = Nothing
|
||||
, status = Loading
|
||||
, apps = []
|
||||
, results = []
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
@ -111,38 +120,55 @@ filterAppsScore : List App -> Maybe Int -> List App
|
|||
filterAppsScore apps score =
|
||||
case score of
|
||||
Just s ->
|
||||
List.filter (\app ->
|
||||
app.status <= s
|
||||
) apps
|
||||
List.filter
|
||||
(\app ->
|
||||
app.status >= s
|
||||
)
|
||||
apps
|
||||
|
||||
Nothing ->
|
||||
apps
|
||||
|
||||
|
||||
filterAppsCategory : List App -> Maybe Category -> List App
|
||||
filterAppsCategory apps category =
|
||||
case category of
|
||||
Just c ->
|
||||
List.filter (\app ->
|
||||
app.category == categories.toString c
|
||||
) apps
|
||||
if c == All then
|
||||
apps
|
||||
|
||||
else
|
||||
List.filter
|
||||
(\app ->
|
||||
app.category == categories.toString c
|
||||
)
|
||||
apps
|
||||
|
||||
Nothing ->
|
||||
apps
|
||||
|
||||
|
||||
filterAppsText : List App -> Maybe String -> List App
|
||||
filterAppsText apps text =
|
||||
case text of
|
||||
Just "" ->
|
||||
apps
|
||||
|
||||
Just t ->
|
||||
List.filter (\app ->
|
||||
String.contains (String.toLower t) (
|
||||
String.toLower app.name ++ String.toLower (
|
||||
Maybe.withDefault "" app.description
|
||||
)
|
||||
List.filter
|
||||
(\app ->
|
||||
String.contains (String.toLower t)
|
||||
(String.toLower app.name
|
||||
++ String.toLower
|
||||
(Maybe.withDefault "" app.description)
|
||||
)
|
||||
)
|
||||
) apps
|
||||
apps
|
||||
|
||||
Nothing ->
|
||||
apps
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
|
@ -151,51 +177,71 @@ update msg model =
|
|||
|
||||
FilterScore filter ->
|
||||
let
|
||||
filter_score = String.toInt filter
|
||||
filter_score =
|
||||
String.toInt filter
|
||||
in
|
||||
( { model
|
||||
| filter_score = filter_score
|
||||
, results = filterAppsScore (
|
||||
filterAppsCategory (
|
||||
filterAppsText model.apps model.filter_text
|
||||
) model.filter_category
|
||||
) (String.toInt filter)
|
||||
}, Cmd.none )
|
||||
( { model
|
||||
| filter_score = filter_score
|
||||
, results =
|
||||
filterAppsScore
|
||||
(filterAppsCategory
|
||||
(filterAppsText model.apps model.filter_text)
|
||||
model.filter_category
|
||||
)
|
||||
(String.toInt filter)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
FilterCategory filter ->
|
||||
let
|
||||
category = categories.fromString filter
|
||||
category =
|
||||
categories.fromString filter
|
||||
in
|
||||
( { model
|
||||
| filter_category = category
|
||||
, results = filterAppsCategory (
|
||||
filterAppsScore (
|
||||
filterAppsText model.apps model.filter_text
|
||||
)model.filter_score
|
||||
) category
|
||||
}, Cmd.none )
|
||||
, results =
|
||||
filterAppsCategory
|
||||
(filterAppsScore
|
||||
(filterAppsText model.apps model.filter_text)
|
||||
model.filter_score
|
||||
)
|
||||
category
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
FilterText filter ->
|
||||
( { model
|
||||
| filter_text = Just filter
|
||||
, results = filterAppsText (
|
||||
filterAppsScore (
|
||||
filterAppsCategory model.apps model.filter_category
|
||||
) model.filter_score
|
||||
) (Just filter)
|
||||
}, Cmd.none )
|
||||
, results =
|
||||
filterAppsText
|
||||
(filterAppsScore
|
||||
(filterAppsCategory model.apps model.filter_category)
|
||||
model.filter_score
|
||||
)
|
||||
(Just filter)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GotApps result ->
|
||||
case result of
|
||||
Ok apps ->
|
||||
( { default_model
|
||||
| status = Success
|
||||
, apps = apps
|
||||
, results = apps }, Cmd.none )
|
||||
| status = Success
|
||||
, apps = apps
|
||||
, results = apps
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Err _ ->
|
||||
( { default_model
|
||||
| status = Failure}, Cmd.none )
|
||||
| status = Failure
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
|
@ -209,14 +255,14 @@ subscriptions model =
|
|||
|
||||
view : Model -> Document Msg
|
||||
view model =
|
||||
{ title = "abra recipes"
|
||||
{ title = "Co-op Cloud Recipes"
|
||||
, body = [ body model ]
|
||||
}
|
||||
|
||||
|
||||
body : Model -> Html Msg
|
||||
body model =
|
||||
div [ class "pt-3" ]
|
||||
div []
|
||||
[ viewApps model
|
||||
]
|
||||
|
||||
|
@ -226,16 +272,16 @@ viewStatusBadge app =
|
|||
let
|
||||
status_class =
|
||||
case app.status of
|
||||
1 ->
|
||||
5 ->
|
||||
"badge-success"
|
||||
|
||||
2 ->
|
||||
4 ->
|
||||
"badge-info"
|
||||
|
||||
3 ->
|
||||
"badge-warning"
|
||||
|
||||
4 ->
|
||||
2 ->
|
||||
"badge-danger"
|
||||
|
||||
_ ->
|
||||
|
@ -289,35 +335,30 @@ viewApp app =
|
|||
app_href =
|
||||
Route.toString <| Route.App_String { app = app.slug }
|
||||
in
|
||||
div [ class "col-md-4 mb-3 col-sm-12" ]
|
||||
[ div [ class "card" ]
|
||||
div [ class "col-md-4 mb-4 col-sm-12" ]
|
||||
[ div [ class "smaller-card" ]
|
||||
[ img [ class "card-img-top", src icon_url, alt ("icon for " ++ app.name) ] []
|
||||
, div [ class "card-body" ]
|
||||
[ h5 [ class "card-title" ]
|
||||
[ a [ href app_href ] [ text app.name ] ]
|
||||
, p [] [ text (ellipsis 100 (withDefault "" app.description)) ]
|
||||
, repository_link
|
||||
, website_link
|
||||
, a [ class "card-link", href app_href ]
|
||||
[ i [ class "fas fa-book" ] []
|
||||
, text "docs"
|
||||
]
|
||||
]
|
||||
, div [ class "card-footer" ]
|
||||
[ span [ class "card-link badge badge-secondary" ] [ text app.category ]
|
||||
, viewStatusBadge app
|
||||
[ h5 [ class "title-container" ]
|
||||
[ a [ class "title", href app_href ] [ text app.name ] ]
|
||||
, p [ class "card-description" ] [ text (ellipsis 100 (withDefault "" app.description)) ]
|
||||
]
|
||||
, div [ class "footer" ]
|
||||
[ viewStatusBadge app, span [ class "badge app-category" ] [ text app.category ] ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
viewCategoryOption : (String, Category) -> Html Msg
|
||||
viewCategoryOption category =
|
||||
option
|
||||
[ value (categories.toString (Tuple.second category) ) ]
|
||||
[ text (categories.toString (Tuple.second category) ) ]
|
||||
|
||||
viewCategory : Model -> ( String, Category ) -> Html Msg
|
||||
viewCategory model category =
|
||||
div
|
||||
[ classList
|
||||
[ ( "category-tile", True )
|
||||
, ( "active", categories.toString (Maybe.withDefault All model.filter_category) == Tuple.first category )
|
||||
]
|
||||
, onClick (FilterCategory (Tuple.first category))
|
||||
]
|
||||
[ text (Tuple.first category) ]
|
||||
|
||||
|
||||
viewApps : Model -> Html Msg
|
||||
|
@ -339,42 +380,70 @@ viewApps model =
|
|||
]
|
||||
|
||||
Success ->
|
||||
div []
|
||||
[ div [ class "row" ]
|
||||
[ div [ class "col-sm-12" ] [
|
||||
div [ class "filter card bg-white" ] [
|
||||
form [ class "card-body form-inline" ] [
|
||||
label [ for "level" ] [ text "Max. level" ]
|
||||
, select [ class "ml-2 mr-4 form-control", id "level", onInput FilterScore ] [
|
||||
option [ ] [ text "any" ]
|
||||
, option [ value "1" ] [ text "1 (production)" ]
|
||||
, option [ value "2" ] [ text "2 (beta)" ]
|
||||
, option [ value "3" ] [ text "3 (alpha)" ]
|
||||
, option [ value "4" ] [ text "4 (pre-alpha)" ]
|
||||
]
|
||||
, label [ for "category" ] [ text "Category" ]
|
||||
, select [ class "ml-2 mr-4 form-control", id "category", onInput FilterCategory ] (
|
||||
option [ ] [ text "any" ]
|
||||
:: (List.map viewCategoryOption categories.list)
|
||||
)
|
||||
, label [ for "text" ] [ text "Search" ]
|
||||
, input [ class "ml-2 form-control", id "text", onInput FilterText ] []
|
||||
, div [ class "ml-auto badge badge-secondary" ] [
|
||||
text (String.fromInt (List.length model.results) ++ " recipes")
|
||||
]
|
||||
div [ class "row justify-content-center" ]
|
||||
[ div [ class "col-md-3", id "filter" ]
|
||||
[ h2 [ class "app-headings" ] [ text "Finding things" ]
|
||||
, form []
|
||||
[ div []
|
||||
[ h3 [] [ text "Search" ]
|
||||
, input [ ariaLabel "search", id "text", onInput FilterText ] []
|
||||
]
|
||||
, div []
|
||||
[ h3 [] [ text "Categories" ]
|
||||
, div [] (List.map (viewCategory model) categories.list)
|
||||
]
|
||||
, div []
|
||||
[ h3 [] [ text "Status" ]
|
||||
, div []
|
||||
[ label [] [ text "Minimum score: " ]
|
||||
, a [ class "help", target "_blank", href "https://docs.coopcloud.tech/abra/recipes/#status-features-score" ] [ text "(help)" ]
|
||||
]
|
||||
, select [ class "search-dropdown", id "level", onInput FilterScore ]
|
||||
[ option [] [ text "any" ]
|
||||
, option [ value "5" ] [ text "5 (amazing)" ]
|
||||
, option [ value "4" ] [ text "4 (good)" ]
|
||||
, option [ value "3" ] [ text "3 (ok)" ]
|
||||
, option [ value "2" ] [ text "2 (basic)" ]
|
||||
, option [ value "1" ] [ text "1 (chaos)" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "row" ]
|
||||
(List.map viewApp
|
||||
(model.results
|
||||
|> List.sortWith
|
||||
(by .status ASC
|
||||
|> andThen (String.toLower << .name) ASC
|
||||
)
|
||||
, div [ class "col-md-6 offset-md-3" ]
|
||||
[ div [ class "row" ]
|
||||
[ div [ class "col-sm-12" ]
|
||||
[ div []
|
||||
[]
|
||||
]
|
||||
]
|
||||
, div [ id "intro", class "card" ]
|
||||
[ div [ class "card-body" ]
|
||||
[ h2 [ class "app-headings card-title" ] [ text "Co-op Cloud Recipes" ]
|
||||
, text "You can use these recipes ("
|
||||
, a
|
||||
[ href
|
||||
"https://docs.coopcloud.tech/glossary/#recipe"
|
||||
]
|
||||
[ text "What's a recipe?"
|
||||
]
|
||||
, text ") with "
|
||||
, a [ href "https://coopcloud.tech" ]
|
||||
[ text "Co-op Cloud"
|
||||
]
|
||||
, text "."
|
||||
]
|
||||
]
|
||||
, div [ class "row" ]
|
||||
(List.map viewApp
|
||||
(model.results
|
||||
|> List.sortWith
|
||||
(by .status DESC
|
||||
|> andThen (String.toLower << .name) ASC
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
, div [ class "col-md-3" ] []
|
||||
]
|
||||
|
||||
|
||||
|
@ -384,9 +453,14 @@ viewApps model =
|
|||
|
||||
loadApps : Cmd Msg
|
||||
loadApps =
|
||||
Http.get
|
||||
{ url = "https://apps.coopcloud.tech/"
|
||||
Http.request
|
||||
{ url = "https://recipes.coopcloud.tech/recipes.json"
|
||||
, expect = Http.expectJson GotApps appListDecoder
|
||||
, headers = [ Http.header "Content-Type" "application/json" ]
|
||||
, body = Http.emptyBody
|
||||
, method = "GET"
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -68,21 +68,20 @@ view :
|
|||
view { page, toMsg } model =
|
||||
{ title = page.title
|
||||
, body =
|
||||
[ div [ class "bg-dark" ]
|
||||
[ nav
|
||||
[ class "navbar navbar-expand-lg sticky-top font-weight-bold"
|
||||
, style "background" "#ff4f88" ]
|
||||
[ a [ class "navbar-brand text-dark", href (Route.toString Route.Top) ]
|
||||
[ img
|
||||
[ src "/logo.png"
|
||||
[ div [ class "background" ]
|
||||
[ nav
|
||||
[ class "navbar navbar-expand-lg sticky-top" ]
|
||||
[ a [ class "font-weight-bold navbar-text", href (Route.toString Route.Top) ]
|
||||
[ img
|
||||
[ src "/logo-2.png"
|
||||
, class "d-inline-block align-top mr-2"
|
||||
, alt ""
|
||||
, width 30
|
||||
, height 30 ] []
|
||||
, text "abra recipes"
|
||||
]
|
||||
, alt ""
|
||||
, width 48
|
||||
, height 16 ] []
|
||||
, text "Co-op Cloud Recipes"
|
||||
]
|
||||
, ul [ class "navbar-nav" ]
|
||||
[ li [ class "nav-tem" ] [ a [ class "nav-link text-dark", href (Route.toString Route.About) ] [ text "about" ] ]
|
||||
[ li [ class "nav-tem" ] [ a [ class "nav-link navbar-text", href (Route.toString Route.About) ] [ text "about" ] ]
|
||||
]
|
||||
]
|
||||
, div [ class "container-fluid" ] page.body
|
||||
|
|
Loading…
Reference in New Issue