Compare commits

...

53 Commits

Author SHA1 Message Date
3wordchant bbf8b3baa5 Merge pull request 'Update node Docker tag to v21 (main)' (#35) from renovate/main-node-21.x into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #35
2024-04-06 18:11:27 +00:00
3wc a5230dfde3 And switch to correct server & secret..
continuous-integration/drone/push Build is passing Details
2024-04-01 20:12:43 -03:00
3wc 9ab689f69e Switch to selfhosted stack-ssh-deploy
continuous-integration/drone/push Build is failing Details
2024-04-01 20:10:42 -03:00
3wc e566a6eef4 Add help link for status score
continuous-integration/drone/push Build is failing Details
2024-04-01 20:08:46 -03:00
Comrade Renovate Bot 209cdc2770 Update dependency elm to v0.19.1-6
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is failing Details
2023-11-02 08:02:07 +00:00
Comrade Renovate Bot 2482caf2e0 Update node Docker tag to v21
continuous-integration/drone/pr Build is failing Details
2023-10-20 07:02:07 +00:00
decentral1se d15df0f9e4 Merge pull request 'Update node Docker tag to v20 (main)' (#34) from renovate/main-node-20.x into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #34
2023-04-24 08:14:37 +00:00
Comrade Renovate Bot 91f8340ac8 Update node Docker tag to v20
continuous-integration/drone/pr Build is failing Details
2023-04-21 07:05:02 +00:00
3wc 6851d2fe1f Add indicator to show active category filter
continuous-integration/drone/push Build is passing Details
Closes #33
2023-03-28 23:08:16 -04:00
3wc e2f9cdd7e2 Tweak text
continuous-integration/drone/push Build is passing Details
2023-03-28 21:11:53 -04:00
3wc 6ed6cbb33e Further filtering tweaks
continuous-integration/drone/push Build is passing Details
2023-03-28 19:35:57 -04:00
3wordchant 41ccf212a9 Merge pull request 'Homepage re-jig' (#32) from filter-rejig into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #32
2023-03-28 17:42:59 +00:00
3wc 382d36d001 Tweak score names, add intro, stylin'
continuous-integration/drone/pr Build was killed Details
2023-03-28 11:27:12 -04:00
3wc 2dec76fbfd elm-format
continuous-integration/drone/pr Build was killed Details
2023-03-28 11:25:53 -04:00
3wc 377c8a7d04 Tweak text 'n style 2023-03-28 11:25:53 -04:00
3wc f7ffc49b76 Some preliminary rejiggin' 2023-03-28 11:25:53 -04:00
3wc 338da444d8 Merge branch 'recipe-versions'
continuous-integration/drone/push Build is passing Details
continuous-integration/drone Build is passing Details
2023-03-28 11:22:35 -04:00
3wc 57b4db9bb2 Add preliminary list of recipe versions
continuous-integration/drone/pr Build is failing Details
2023-03-28 11:22:10 -04:00
3wc 70307aad9e Attempt at parsing "version" from JSON 2023-03-28 11:22:10 -04:00
3wordchant af5565dcc5 Merge pull request 'Reinstate score badges, fix sorting' (#29) from recipe-scores into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #29
2023-03-28 15:20:22 +00:00
3wc 4a375f3d5f Make logos bigger on recipe detail page
continuous-integration/drone/push Build is passing Details
2023-03-28 01:07:06 -04:00
3wc d8b2192240 Reinstate score badges, fix sorting
continuous-integration/drone/pr Build is failing Details
2023-03-26 18:47:47 -04:00
3wc 65ef1d2772 Switch back to Nginx, fix direct URLs
continuous-integration/drone/push Build is passing Details
2023-03-23 13:43:43 -04:00
KawaiiPunk c50635319b
Small CSS change to make recipe logo smaller on homepage
continuous-integration/drone/push Build is passing Details
2023-03-23 17:20:04 +00:00
3wc 32366b7bf7 Whoops, wrong path
continuous-integration/drone/push Build is passing Details
2023-01-25 10:36:14 -08:00
3wc dbb701a2a4 Fix image (again)
continuous-integration/drone/push Build is passing Details
2023-01-25 10:30:53 -08:00
3wc b761f39247 Fix embarrassing deployment snafus
continuous-integration/drone/push Build is passing Details
2023-01-24 09:04:19 -08:00
3wc a7435c3d37 Switch to static deployment
continuous-integration/drone/push Build was killed Details
Closes #9

Generates static HTML + JS version of the Elm app, and serves it using
thhtpd, for bonus permacomputing joy.
2023-01-22 20:41:30 -08:00
3wc 429cb371a8 Add AUTHORS 2023-01-22 20:28:35 -08:00
3wc 774c59bfa8 Update package-lock.json 2023-01-22 20:28:14 -08:00
3wc 39c3122230 Merge remote-tracking branches 'origin/renovate/main-chokidar-cli-3.x' and 'origin/renovate/main-elm-0.x' 2023-01-22 20:26:27 -08:00
3wc a7ea4207ce Update attribution 2023-01-22 20:25:11 -08:00
Comrade Renovate Bot 6769455c27 Update dependency chokidar-cli to v3
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing Details
2023-01-20 08:15:30 +00:00
Comrade Renovate Bot 984c205299 Update dependency elm to v0.19.1-5
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing Details
2023-01-20 08:14:25 +00:00
decentral1se 5b28441102 Merge pull request 'Configure Renovate' (#19) from renovate/configure into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #19
2023-01-19 10:55:02 +00:00
Comrade Renovate Bot d278ec2ad4 Add renovate.json
continuous-integration/drone/pr Build is failing Details
2023-01-19 08:11:18 +00:00
3wc 3f10abe1c5 Fix Drone badge links
[ci skip]
2023-01-08 19:06:45 -08:00
3wc 1a7ade0f76 Drop CORS proxy
continuous-integration/drone/push Build is passing Details
RE: #18
2023-01-08 19:04:39 -08:00
3wc 0777f7c008 Old URL work pls
continuous-integration/drone/push Build is passing Details
continuous-integration/drone Build is passing Details
2022-05-08 17:43:36 +01:00
3wc 69a7fbc036 More URL fixin'
continuous-integration/drone/push Build is passing Details
2022-05-04 10:30:54 +01:00
3wc 4c5e151615 Rename stack for manual deploy
continuous-integration/drone/push Build is passing Details
2022-05-04 10:25:46 +01:00
3wc 9548d0715e Fix deployment
continuous-integration/drone/push Build is passing Details
2022-05-04 10:25:32 +01:00
3wc 68669b7445 Add manual deploy instructions
continuous-integration/drone/push Build was killed Details
2022-05-04 10:25:18 +01:00
decentral1se 1c306e8607
disable for now
continuous-integration/drone/push Build was killed Details
2022-05-04 11:20:19 +02:00
decentral1se 375cafb946
more naming fixups
continuous-integration/drone/push Build was killed Details
2022-05-04 11:19:28 +02:00
decentral1se 910d0bf2ae
fixup that title
continuous-integration/drone/push Build is passing Details
2022-05-04 11:16:14 +02:00
3wc 31f8a0be69 Add missing arguments to Http.expect 🤔🤔🤔
continuous-integration/drone/push Build is passing Details
2022-05-04 09:58:33 +01:00
3wc 9c4522dceb Switch URL, upd8 README
continuous-integration/drone/push Build is failing Details
2022-05-03 18:57:37 +01:00
decentral1se 83f0919cd8 Merge pull request 'Change dev.site to recipes.site' (#16) from em_change-site into main
continuous-integration/drone/push Build was killed Details
Reviewed-on: #16
2022-05-03 16:06:46 +00:00
Eiven Mitchell d58ecbfd3c
Change dev.site to recipes.site
continuous-integration/drone/pr Build is failing Details
2022-05-03 10:57:30 -04:00
Eiven Mitchell 3b57477e62 Merge pull request 'Start to the style changeover via Figma designs' (#15) from em_add-styles into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #15
2022-04-25 18:53:14 +00:00
Eiven Mitchell 72d1822cf0
Revamp of the abra recipes styling
continuous-integration/drone/pr Build is failing Details
* Changes the shape of the cards and the placement of the inside
  content.
* New background color
* Restructures the grid that the apps sit in
* Adds a new categories bar to the left
2022-04-21 15:13:04 -04:00
3wordchant f10a572d6f Merge pull request 'Switching out "app" for "recipes"' (#13) from em_issue-12 into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #13
2022-03-24 13:49:40 +00:00
18 changed files with 3823 additions and 307 deletions

View File

@ -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

8
AUTHORS.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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": {

View File

@ -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

17
nginx.conf Normal file
View File

@ -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;
}
}

3492
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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

BIN
public/logo-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View File

@ -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;
}

3
renovate.json Normal file
View File

@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View File

@ -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 ")"
]

View File

@ -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 "")

View File

@ -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
}

View File

@ -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