[HYPERAPP] hyperapp-tutorial-step-2 (ok)

https://codesandbox.io/s/lu11w

C:\xampp\htdocs\hyperlist\index.js

// -- IMPORTS --
import { h, app } from "hyperapp"
import { Http } from "hyperapp-fx"
import { interval } from "@hyperapp/time"
// -- ACTIONS --
const FetchStories = state => [
  { ...state, fetching: true },
  Http({
    url: `https://zaceno.github.io/hatut/data/${state.filter.toLowerCase()}.json`,
    response: "json",
    action: GotStories
  })
]
const StartEditingFilter = state => ({ ...state, editingFilter: true })
const StopEditingFilter = state =>
  FetchStories({ ...state, editingFilter: false })
const SetFilter = (state, word) => ({ ...state, filter: word })
const SelectStory = (state, id) => ({
  ...state,
  reading: id,
  stories: {
    ...state.stories,
    [id]: {
      ...state.stories[id],
      seen: true
    }
  }
})
const GotStories = (state, response) => {
  const stories = {}
  response = {
    "112": { title: "The Ocean is Sinking", author: "Kat Stropher" },
    "113": { title: "Ocean life is brutal", author: "Surphy McBrah" },
    "114": {
      title: "Family friendly fun at the ocean exhibit",
      author: "Guy Prosales"
    }
  }
  Object.keys(response).forEach(id => {
    stories[id] = { ...response[id], seen: false }
    if (state.stories[id] && state.stories[id].seen) {
      stories[id].seen = true
    }
  })
  const reading = stories[state.reading] ? state.reading : null
  return {
    ...state,
    stories,
    reading,
    fetching: false
  }
}
const ToggleAutoUpdate = state => ({ ...state, autoUpdate: !state.autoUpdate })
// -- VIEWS ---
const emphasize = (word, string) =>
  string.split(" ").map(x => {
    if (x.toLowerCase() === word.toLowerCase()) {
      return h("em", {}, x + " ")
    } else {
      return x + " "
    }
  })
const StoryThumbnail = props =>
  h(
    "li", {
      onClick: [SelectStory, props.id],
      class: {
        unread: props.unread,
          reading: props.reading
      }
    },
    [
      h("p", { class: "title" }, emphasize(props.filter, props.title)),
      h("p", { class: "author" }, props.author)
    ]
  )
const StoryList = props =>
  h("div", { class: "stories" }, [
    props.fetching &&
    h("div", { class: "loadscreen" }, [h("div", { class: "spinner" })]),
    h(
      "ul", {},
      Object.keys(props.stories).map(id =>
        StoryThumbnail({
          id,
          title: props.stories[id].title,
          author: props.stories[id].author,
          unread: !props.stories[id].seen,
          reading: props.reading === id,
          filter: props.filter
        })
      )
    )
  ])
const Filter = props =>
  h("div", { class: "filter" }, [
    "Filter:",
    props.editingFilter ?
    h("input", {
      type: "text",
      value: props.filter,
      onInput: [SetFilter, event => event.target.value] // <----
    }) :
    h("span", { class: "filter-word" }, props.filter),
    props.editingFilter ?
    h("button", { onClick: StopEditingFilter }, "\u2713") :
    h("button", { onClick: StartEditingFilter }, "\u270E")
  ])
const StoryDetail = props =>
  h("div", { class: "story" }, [
    props && h("h1", {}, props.title),
    props &&
    h(
      "p", {},
      `
        Lorem ipsum dolor sit amet, consectetur adipiscing
        elit, sed do eiusmod tempor incididunt ut labore et
        dolore magna aliqua. Ut enim ad minim veniam, qui
        nostrud exercitation ullamco laboris nisi ut aliquip
        ex ea commodo consequat.
      `
    ),
    props && h("p", { class: "signature" }, props.author)
  ])
const AutoUpdate = props =>
  h("div", { class: "autoupdate" }, [
    "Auto update: ",
    h("input", {
      type: "checkbox",
      checked: props.autoUpdate, // <---
      onInput: ToggleAutoUpdate // <---
    })
  ])
const Container = content => h("div", { class: "container" }, content)
// -- RUN --
app({
  node: document.getElementById("app"),
  view: state =>
    Container([
      Filter(state),
      StoryList(state),
      StoryDetail(state.reading && state.stories[state.reading]),
      AutoUpdate(state)
    ]),
  init: FetchStories({
    editingFilter: false,
    autoUpdate: false,
    filter: "ocean",
    reading: null,
    stories: {}
  }),
  subscriptions: state => [
    state.autoUpdate && interval(FetchStories, { delay: 5000 })
  ]
})

C:\xampp\htdocs\hyperlist\index.html

<!DOCTYPE html>
<html>
<head>
  <title>Hyperapp Tutorial - Step 1</title>
  <link rel="stylesheet" href="style.css" />
  <script type="module" src="./index.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

C:\xampp\htdocs\hyperlist\package.json

{
  "name": "hyperapp-tutorial-step-2",
  "version": "1.0.0",
  "description": "",
  "main": "index.html",
  "scripts": {
    "start": "parcel index.html --open",
    "build": "parcel build index.html"
  },
  "dependencies": {
    "@hyperapp/time": "^1.0.0",
    "hyperapp": "2.0.3",
    "hyperapp-fx": "2.0.0-beta.1"
  },
  "devDependencies": {
    "@babel/core": "7.2.0",
    "parcel-bundler": "^1.6.1"
  },
  "keywords": []
}

C:\xampp\htdocs\hyperlist\style.css

.container {
  font-family: sans-serif;
  color: #666;
  background-color: #ccc;
  width: 100%;
  display: grid;
  grid-template-columns: 10px 340px 10px auto 10px;
  grid-template-rows: 10px 30px 10px 250px 10px 20px 10px;
}
.filter {
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  position: relative;
  line-height: 30px;
  padding-top: 1px;
}
.stories {
  grid-column: 2 / 3;
  grid-row: 4 / 5;
  position: relative;
}
.autoupdate {
  grid-column: 2/3;
  grid-row: 6/7;
}
.story {
  grid-column: 4 / 5;
  grid-row: 2 / 7;
  position: relative;
}
.filter .filter-word {
  font-weight: bold;
  text-transform: uppercase;
  font-size: 16px;
  margin-left: 15px;
}
.filter input[type='text'] {
  position: absolute;
  box-sizing: border-box;
  border: none;
  top: 0;
  left: 50px;
  height: 30px;
  width: 245px;
  padding: 0;
  padding-left: 5px;
  padding-top: 2px;
  font-size: 16px;
  text-transform: uppercase;
  font-weight: bold;
  font-family: sans-serif;
}
.filter button {
  position: absolute;
  top: 0;
  right: 0;
  box-sizing: border-box;
  width: 30px;
  height: 30px;
  font-size: 20px;
  border-radius: 4px;
  background: #ddd;
  border: 1px gray solid;
  box-shadow: 0px 0px 4px #888;
}
.filter button:hover {
  background: lemonchiffon;
}
.filter button:active {
  color: #666;
  box-shadow: none;
}
.stories {
  height: 100%;
}
.stories ul {
  margin: 0;
  padding: 10px;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  list-style-type: none;
  background-color: #fff;
  box-shadow: inset 0px 0px 5px #333;
  border-radius: 4px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.stories li {
  border: solid 1px #ccc;
  border-radius: 6px;
  border-left-width: 7px;
  box-shadow: 0px 0px 5px #ccc;
  margin-bottom: 10px;
}
.stories li.reading {
  background-color: lemonchiffon;
  border-left-color: orange;
}
.stories li.unread {
  border-left-color: cornflowerblue;
}
.stories li:hover {
  background-color: lemonchiffon;
}
.stories p.title {
  margin: 10px 10px 0px 10px;
}
.stories em {
  font-weight: bold;
  font-style: normal;
}
.stories p.author {
  margin: 0px 10px 10px 20px;
  font-style: italic;
  font-family: serif;
}
.stories .loadscreen {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  background-color: rgba(255, 255, 255, 0.7);
  text-align: center;
}
.spinner {
  display: inline-block;
  width: 64px;
  height: 64px;
  position: absolute;
  top: 40%;
  margin-left: -32px;
}
.spinner:after {
  content: ' ';
  display: block;
  width: 46px;
  height: 46px;
  margin: 1px;
  border-radius: 50%;
  border: 5px solid #fff;
  border-color: #ccc transparent #ccc transparent;
  animation: spinner 1.2s linear infinite;
}
@keyframes spinner {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
.autoupdate {
  text-align: center;
}
.story {
  background: #fff;
  padding: 50px;
  overflow-y: scroll;
  overflow-x: hidden;
  box-shadow: inset 0px 0px 5px #444;
}
.story h1 {
  background-color: limegreen;
  color: #fff;
  margin: -40px -40px 40px -40px;
  padding: 50px 50px 20px 50px;
}
.story p {
  text-align: justify;
  font-family: serif;
  line-height: 1.4em;
}
.story p.signature {
  text-align: right;
  font-style: italic;
  font-size: 1.1em;
}

Last updated

Navigation

Lionel

@Copyright 2023