TodoMVC example

This commit is contained in:
weiss
2020-03-30 15:18:46 +02:00
parent a9b452be04
commit 04b20179e3
13 changed files with 1031 additions and 119 deletions

View File

@@ -1,18 +1,19 @@
# pure-vue # pure-vue
## Project setup
## Project setup ![npm](https://img.shields.io/npm/v/pure-vue)
``` ```
npm install npm install
``` ```
### Compiles and hot-reloads for development ### Compiles and hot-reloads for development
1. spago install arrays 1. vue create my_project
3. spago install record-extra 2. cd my_project
1. vue create my_cool_project
2. cd my_cool_project
3. spago init 3. spago init
4. npm install vue-cli-plugin-pure-vue 4. npm install vue-cli-plugin-pure-vue
5. vue invoke vue-cli-plugin-pure-vue 5. vue invoke vue-cli-plugin-pure-vue
## Example Application ## Example Application
Check out Todo List [Example Application](https://https://github.com/smarwei/pure-vue-todo-example). The Plugin will install a few examples (you can find the sources in generator/templateExampleApp/src)
Have a look at them [Example Applications](pure-vue-todo-mvc.epizy.com).

View File

@@ -8,8 +8,12 @@ You can edit this file as you like.
, "console" , "console"
, "effect" , "effect"
, "psci-support" , "psci-support"
, "record-extra" , "web-html"
, "typelevel-prelude" , "web-storage"
, "simple-json"
, "foreign"
, "partial"
, "stringutils"
] ]
, packages = ./packages.dhall , packages = ./packages.dhall
, sources = [ "src/**/*.purs", "node_modules/vue-cli-plugin-pure-vue/src/**/*.purs", "test/**/*.purs" ] , sources = [ "src/**/*.purs", "node_modules/vue-cli-plugin-pure-vue/src/**/*.purs", "test/**/*.purs" ]

View File

@@ -1,52 +1,47 @@
module App where module App where
import Prelude import Prelude
import Data.Maybe (Maybe(..))
import Effect (Effect) import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect) import Effect.Unsafe (unsafePerformEffect)
import Effect.Vue.Ref as Ref import Effect.Vue.Ref as Ref
import Effect.Vue.Hooks as Hooks import Effect.Vue.Hooks as Hooks
import Effect.Class.Console (logShow) import Effect.Class.Console (logShow)
import Data.Array (snoc) import Web.HTML (window)
import Web.HTML.Window (history, location)
import Web.HTML.Location (pathname)
import Web.HTML.History (pushState, state, DocumentTitle(..), URL(..))
type Product = { id :: Int, title :: String, price :: Number } setView :: Ref.Ref String -> String -> Effect Unit
createProduct :: Array Product -> Int -> String -> Number -> Array Product setView route vName = Ref.write vName route
createProduct products id title price = snoc products { id, title, price }
execCreateProduct :: Ref.Ref (Array Product) -> String -> Number -> Effect Unit linkClicked :: Ref.Ref String -> String -> Effect Unit
execCreateProduct products title price = do linkClicked route link = do
p <- Ref.read products setView route link
let p' = createProduct p 42 title (23.666) win <- window
Ref.write p' products hist <- history win
st <- state hist
Ref.write link route
pushState st (DocumentTitle link) (URL link) hist
incRelease :: Ref.Ref Int -> Effect Unit getPathname :: Effect String
incRelease release = Ref.modify_ (\x -> x + 1) release getPathname = do
win <- window
loc <- location win
pathname loc
compTest :: Ref.Ref Int -> Effect Int setup :: forall a. {} -> Effect a
compTest release = do
r <- Ref.read release
pure (r + 1)
type Props = {}
setup :: forall a. Props -> Effect a
setup props = unsafePerformEffect do setup props = unsafePerformEffect do
products :: Ref.Ref (Array Product) <- Ref.new [] r <- getPathname
showUserInfo <- Ref.new false logShow r
release <- Ref.new 9 route <-Ref.new r
computedTest <- Ref.computed $ compTest release
Hooks.onMounted mounted Hooks.onMounted mounted
Ref.vReturn { products Ref.vReturn { route
, showUserInfo , linkClicked: linkClicked route
, release
, computedTest
, incRelease: (incRelease release)
, addProduct: (execCreateProduct products)
} }
mounted :: Effect Unit mounted :: Effect Unit
mounted = do mounted = logShow "I am an onMounted hook"
logShow "estasetase"

View File

@@ -1,31 +1,64 @@
<template> <template>
<div id="app">
<div class="navbar container">
<div class="navbar-el" @click="linkClicked('/')()">Vue</div>
-
<div class="navbar-el" @click="linkClicked('/pure_vue')()">pure-vue</div>
-
<div class="navbar-el" @click="linkClicked('/counter')()">Counter</div>
-
<div class="navbar-el" @click="linkClicked('/todo')()">TodoMVC</div>
</div>
<!--img alt="Vue logo" src="./assets/logo.png"-->
<!--div class="container"-->
<div> <div>
<button <HelloWorld
target="_blank" v-if="route === '/'"
@click="incRelease(); addProduct('mytit')(32)();" msg="Welcome to Your Vue.js App"
/> />
<span class="mr-2">Latest Release {{ release }}</span> <HelloWorld
v-else-if="route === '/pure_vue'"
ctest - {{ computedTest }} - msg="Welcome to Your Vue.js App"
<privacy-policy />
:release="42" <counter v-else-if="route === '/counter'" />
></privacy-policy> <todo :prop_filter="'filter'" v-else />
<!--router-view </div>
:release="release"
></router-view-->
{{ products }}
</div> </div>
</template> </template>
<script> <script>
import { setup } from '@/App.purs' import { setup } from '@/App.purs'
import PrivacyPolicy from './views/PrivacyPolicy' import HelloWorld from './components/HelloWorld'
import Todo from './views/Todo'
export default { export default {
name: 'App', name: 'App',
components: { components: {
PrivacyPolicy, HelloWorld,
Todo,
}, },
setup setup,
}; };
</script> </script>
<style>
.container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.navbar {
padding: 20px;
}
.navbar > div {
color: rgb(184, 40, 40);
font-size: 18px;
}
.navbar-el {
padding: 0px 20px;
cursor: pointer;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg fill="white" version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="319.84px" height="64.684px" viewBox="0 0 319.84 64.684" enable-background="new 0 0 319.84 64.684" xml:space="preserve">
<polygon points="65.619,50.241 59.156,44.206 30.775,44.206 37.238,50.241 "/>
<polygon points="37.238,29.085 30.775,35.12 59.156,35.12 65.619,29.085 "/>
<polygon points="65.617,19.997 59.156,13.96 30.777,13.96 37.238,19.997 "/>
<path d="M27.789,25.97l-4.27-4.271L7.689,37.53c-0.568,0.567-0.882,1.328-0.88,2.134c0,0.808,0.312,1.565,0.88,2.133
l15.83,15.83l4.27-4.267L14.094,39.663L27.789,25.97z"/>
<path d="M88.705,22.407L72.881,6.575l-4.268,4.269L82.301,24.54L68.613,38.235l4.268,4.269l15.824-15.827
c0.57-0.572,0.885-1.331,0.885-2.139C89.588,23.731,89.275,22.976,88.705,22.407"/>
<path d="M123.176,23.817c1.314,1.099,1.972,2.791,1.972,5.077s-0.671,3.96-2.012,5.021
c-1.341,1.063-3.398,1.592-6.17,1.592h-3.348v5.537h-3.185V22.17h6.479C119.775,22.17,121.862,22.721,123.176,23.817
M120.922,31.607c0.639-0.675,0.958-1.665,0.958-2.97c0-1.306-0.404-2.228-1.215-2.768c-0.809-0.54-2.079-0.81-3.806-0.81h-3.241
v7.56h3.7C119.081,32.621,120.282,32.283,120.922,31.607"/>
<path d="M136.379,36.725c0.863,1.008,2.033,1.512,3.51,1.512c1.474,0,2.646-0.504,3.51-1.512
c0.863-1.009,1.294-2.377,1.294-4.104v-10.45h3.188v10.584c0,2.718-0.748,4.811-2.242,6.276c-1.494,1.467-3.41,2.201-5.75,2.201
c-2.34,0-4.258-0.734-5.752-2.201c-1.494-1.467-2.241-3.56-2.241-6.276V22.171h3.186V32.62
C135.083,34.348,135.515,35.717,136.379,36.725"/>
<path d="M171.18,28.3c0,3.114-1.355,5.076-4.076,5.886l4.941,6.857h-4.051l-4.508-6.345H159.3v6.345h-3.185V22.171
h7.018c2.88,0,4.943,0.486,6.186,1.458C170.561,24.601,171.18,26.158,171.18,28.3 M166.942,30.973
c0.646-0.559,0.973-1.454,0.973-2.688c0-1.231-0.334-2.079-1-2.538s-1.854-0.688-3.563-0.688h-4.051v6.75h3.969
C165.069,31.81,166.293,31.531,166.942,30.973"/>
<polygon points="192.268,22.171 192.268,25.168 182.198,25.168 182.198,30.163 191.243,30.163 191.243,32.998
182.198,32.998 182.198,38.047 192.592,38.047 192.592,41.044 179.012,41.044 179.012,22.171 "/>
<path d="M205.62,24.655c-0.929,0-1.688,0.188-2.281,0.567c-0.594,0.378-0.892,0.949-0.892,1.715
c0,0.765,0.298,1.35,0.892,1.755c0.595,0.405,1.856,0.841,3.793,1.309c1.936,0.468,3.393,1.125,4.375,1.972
c0.979,0.847,1.471,2.093,1.471,3.739c0,1.647-0.621,2.983-1.863,4.01c-1.24,1.025-2.871,1.538-4.887,1.538
c-2.953,0-5.572-1.018-7.857-3.051l1.998-2.402c1.908,1.656,3.891,2.483,5.939,2.483c1.027,0,1.842-0.22,2.445-0.661
c0.602-0.44,0.904-1.024,0.904-1.755c0-0.729-0.285-1.296-0.852-1.701c-0.567-0.404-1.543-0.773-2.93-1.105
c-1.387-0.334-2.44-0.641-3.158-0.918c-0.721-0.279-1.36-0.645-1.918-1.094c-1.116-0.847-1.674-2.143-1.674-3.889
c0-1.745,0.635-3.092,1.902-4.036c1.271-0.944,2.84-1.418,4.711-1.418c1.205,0,2.402,0.198,3.594,0.595
c1.187,0.396,2.213,0.954,3.076,1.674l-1.699,2.402c-0.561-0.503-1.315-0.918-2.27-1.241
C207.486,24.817,206.547,24.655,205.62,24.655"/>
<path d="M228.771,38.154c1.099,0,2.043-0.184,2.836-0.553c0.791-0.369,1.619-0.959,2.481-1.77l2.054,2.106
c-1.998,2.213-4.425,3.319-7.276,3.319s-5.22-0.918-7.101-2.754c-1.882-1.836-2.821-4.157-2.821-6.966
c0-2.808,0.959-5.147,2.875-7.02c1.918-1.872,4.338-2.809,7.264-2.809s5.369,1.08,7.33,3.24l-2.025,2.214
c-0.899-0.864-1.75-1.467-2.551-1.809s-1.742-0.514-2.822-0.514c-1.908,0-3.51,0.617-4.805,1.851
c-1.297,1.232-1.945,2.808-1.945,4.725s0.646,3.52,1.933,4.807C225.482,37.512,227.008,38.154,228.771,38.154"/>
<path d="M258.336,28.3c0,3.114-1.358,5.076-4.076,5.886l4.939,6.857h-4.049l-4.511-6.345h-4.187v6.345h-3.185V22.171
h7.019c2.881,0,4.942,0.486,6.185,1.458C257.715,24.601,258.336,26.158,258.336,28.3 M254.098,30.973
c0.646-0.559,0.972-1.454,0.972-2.688c0-1.231-0.332-2.079-0.998-2.538c-0.665-0.459-1.856-0.688-3.565-0.688h-4.051v6.75h3.971
C252.225,31.81,253.45,31.531,254.098,30.973"/>
<rect x="266.167" y="22.171" width="3.187" height="18.873"/>
<path d="M290.575,23.817c1.313,1.099,1.971,2.791,1.971,5.077s-0.67,3.96-2.012,5.021
c-1.341,1.063-3.396,1.592-6.168,1.592h-3.351v5.537h-3.185V22.17h6.479C287.172,22.17,289.26,22.721,290.575,23.817 M288.32,31.607
c0.64-0.675,0.959-1.665,0.959-2.97c0-1.306-0.405-2.228-1.217-2.768c-0.808-0.54-2.078-0.81-3.806-0.81h-3.241v7.56h3.7
C286.479,32.621,287.682,32.283,288.32,31.607"/>
<polygon points="306.667,25.087 306.667,41.044 303.479,41.044 303.479,25.087 297.756,25.087 297.756,22.171
312.39,22.171 312.39,25.087 "/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,58 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,11 @@
import Vue from 'vue'
import App from './App.vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')

View File

@@ -1,24 +0,0 @@
module PrivacyPolicy where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import Effect.Vue.Ref as Ref
import Record.Extra as Record
import Data.Array as Arr
import Type.Row (RProxy(..))
type Props = ( release :: Int )
-- this to lib
-- type RecordProps = Record.Record Props
props = Record.keys (RProxy :: RProxy Props)
privacyPolicy :: String -> String
privacyPolicy policyRaw = policyRaw
buildSetup :: forall a. String -> _ -> Effect a
buildSetup policyRaw props = unsafePerformEffect do
dialog <- Ref.new true
Ref.vReturn { dialog, privacyPolicy: privacyPolicy policyRaw }

View File

@@ -1,31 +0,0 @@
<template>
<v-container>
<v-layout>
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
Privacy Policy r {{ release }}
</v-card-title>
<v-card-text v-html="privacyPolicy()">
</v-card-text>
</v-card>
</v-layout>
</v-container>
</template>
<script>
import { buildSetup, props } from '@/views/PrivacyPolicy.purs'
export default {
name: 'PrivacyPolicy',
props,
setup: buildSetup('myprvipolicy'),
}
</script>
<style>
</style>

View File

@@ -0,0 +1,195 @@
module Todo where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import Effect.Vue.Ref as Ref
import Props (PropType(..), Props)
import Data.Array (snoc, filter)
import Data.Foldable (any, all)
import Data.Maybe (Maybe(..), fromJust)
import Partial.Unsafe (unsafePartial)
import Web.HTML (window)
import Web.HTML.Window (localStorage, location)
import Web.HTML.Location (hash, setHash)
import Web.Storage.Storage (getItem, setItem)
import Simple.JSON as JSON
import Data.Either (Either(..))
import Data.String.Utils (startsWith)
import Data.String.CodePoints (drop)
import Effect.Console (log)
props :: Props
props = [String "prop_filter"]
data TaskStatus = Active | Completed
instance showTaskStatus :: Show TaskStatus where
show Active = "active"
show Completed = "completed"
derive instance eqTaskStatus :: Eq TaskStatus
type Filter = Maybe TaskStatus
readFilter :: String -> Filter
readFilter ts | ts == "active" = Just Active
readFilter ts | ts == "completed" = Just Completed
readFilter _ = Nothing
type Task = { title :: String, status :: TaskStatus }
type Tasks = Array Task
type SerializableTask = { title :: String, status :: String }
toSerializableTask :: Task -> SerializableTask
toSerializableTask t = t { status = show t.status }
fromSerializableTask :: SerializableTask -> Task
fromSerializableTask t = t { status = unsafePartial $ fromJust $ readFilter t.status }
-- Only add a task if it is not already on this list
addTask :: Tasks-> Task -> Tasks
addTask tasks { title: "", status: _ } = tasks
addTask tasks task =
if any (\t -> t.title == task.title) tasks
then tasks
else snoc tasks task
execAddTask :: Ref.Ref Tasks -> Ref.Ref String -> Effect Unit
execAddTask tasks taskTitle = do
ts <- Ref.read tasks
title <- Ref.read taskTitle
let t = { title, status: Active }
let ts' = addTask ts t
Ref.write ts' tasks
updateTasksJs tasks
updateTasksJs :: Ref.Ref Tasks -> Effect Unit
updateTasksJs tasks = do
win <- window
store <- localStorage win
tasks' <- Ref.read tasks
setItem "todos-pure-vue" (JSON.writeJSON $ toSerializableTask <$> tasks') store
restoreTasks :: Effect Tasks
restoreTasks = do
win <- window
store <- localStorage win
i <- getItem "todos-pure-vue" store
case i of
Just items -> do
case JSON.readJSON items of
Right (st :: Array SerializableTask) -> pure $ fromSerializableTask <$> st
Left e -> do
log $ "error: " <> show e
pure []
Nothing -> pure []
toggleAll :: Ref.Ref Tasks -> Effect Unit
toggleAll tasks = do
tasks' <- Ref.read tasks
if allCompleted tasks'
then Ref.modify_ (setStatus Active) tasks
else Ref.modify_ (setStatus Completed) tasks
updateTasksJs tasks
where
setStatus :: TaskStatus -> Tasks -> Tasks
setStatus status tasks' = map (\t -> t { status = status }) tasks'
allCompleted :: Tasks -> Boolean
allCompleted tasks' = all (\task -> task.status == Completed) tasks'
toggleTask :: Ref.Ref Tasks -> Task -> Effect Unit
toggleTask tasks task = do
Ref.modify_ (\ts -> toggleTask' ts task) tasks
updateTasksJs tasks
toggleTask' :: Tasks -> Task -> Tasks
toggleTask' tasks' task = do
t <- tasks'
if t.title == task.title
then pure $ t { status = if t.status == Completed then Active else Completed }
else pure t
removeTask :: Ref.Ref Tasks -> Task -> Effect Unit
removeTask tasks task = Ref.modify_ (filter (\t -> t.title /= task.title)) tasks
tasksToJs :: Tasks -> Array { title :: String, status :: String }
tasksToJs tasks = map (\t -> t { status = show t.status }) tasks
clearCompleted :: Ref.Ref Tasks -> Effect Unit
clearCompleted tasks = do
Ref.modify_ clearCompleted' tasks
updateTasksJs tasks
where clearCompleted' :: Tasks -> Tasks
clearCompleted' tasks' = filter (\t -> t.status /= Completed) tasks'
filterTasks :: Ref.Ref Tasks -> Ref.Ref Filter -> Effect (Array { title :: String, status :: String })
filterTasks tasks taskFilter = do
t <- Ref.read tasks
f <- Ref.read taskFilter
pure $ tasksToJs $ filterTasks' t f
filterTasksLeft :: Ref.Ref Tasks -> Effect (Array { title :: String, status :: String })
filterTasksLeft tasks = do
t <- Ref.read tasks
pure $ tasksToJs $ filterTasks' t $ Just Active
filterTasks' :: Tasks -> Filter -> Tasks
filterTasks' tasks' Nothing = tasks'
filterTasks' tasks' (Just taskFilter') = filter (\t -> t.status == taskFilter') tasks'
setFilter :: Ref.Ref Filter -> Filter -> Effect Unit
setFilter filter toFilter = do
win <- window
loc <- location win
setHash ("/" <> showFilter toFilter) loc
Ref.write toFilter filter
where showFilter :: Filter -> String
showFilter (Just Active) = "active"
showFilter (Just Completed) = "completed"
showFilter Nothing = ""
initFilters :: Effect (Ref.Ref Filter)
initFilters = do
win <- window
loc <- location win
hsh <- hash loc
let filter = if startsWith "#/" hsh
then readFilter $ drop 2 hsh
else Nothing
taskFilter :: Ref.Ref Filter <- Ref.new filter
pure taskFilter
setup :: forall a. Props -> Effect a
setup props = unsafePerformEffect do
taskTitle <- Ref.new ""
tasksRestored <- restoreTasks
tasks :: Ref.Ref Tasks <- Ref.new tasksRestored
taskFilter <- initFilters
filteredTasks <- Ref.computed $ filterTasks tasks taskFilter
tasksLeft <- Ref.computed $ filterTasksLeft tasks
Ref.vReturn { taskTitle
, addTask: execAddTask tasks taskTitle
, removeTask: removeTask tasks
, toggleTask: toggleTask tasks
, toggleAll: toggleAll tasks
, clearCompleted: clearCompleted tasks
, filter: taskFilter
, filteredTasks
, setFilter: setFilter taskFilter
, tasksLeft
, tasks
, filterIs: filterIs taskFilter
, fNothing: Nothing
, fActive: Just Active
, fCompleted: Just Completed
}
where
filterIs :: Ref.Ref Filter -> Filter -> Effect Boolean
filterIs fRef fa = do
fb <- Ref.read fRef
pure $ fa == fb

View File

@@ -0,0 +1,619 @@
<template>
<div>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input
v-model="taskTitle"
@keyup.enter="addTaskJs()"
class="new-todo"
placeholder="What needs to be done?"
autofocus
>
</header>
<section class="main">
<input @click="toggleAll()" id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list"
v-for="task in filteredTasks"
:key="task.title"
>
<li :class="{ completed: task.status === 'completed' }">
<div class="view">
<input :checked="task.status === 'completed'" @click="toggleTask(task)()" class="toggle" type="checkbox" />
<label>{{ task.title }}</label>
<button @click="removeTask(task)()" class="destroy"></button>
</div>
</li>
</ul>
<footer v-show="tasks.length > 0" class="footer">
<span class="todo-count">
<strong>{{ tasksLeft.length }}</strong> items left
</span>
<ul class="filters">
<li>
<span @click="setFilter(fNothing)()"
:class="{ selected: filterIs(fNothing)() }"
>
All
</span>
</li>
<li>
<span @click="setFilter(fActive)()"
:class="{ selected: filterIs(fActive)() }"
>
Active
</span>
</li>
<li>
<span @click="setFilter(fCompleted)()"
:class="{ selected: filterIs(fCompleted)() }"
>
Completed
</span>
</li>
</ul>
<button
@click="clearCompleted()"
class="clear-completed"
>
Clear completed
</button>
</footer>
</section>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="https://github.com/smarwei">Arne Wei&szlig;</a></p>
<p>Based on TodoMVC templates</p>
<p><span style="text-decoration: line-through">Part of <a href="http://todomvc.com">TodoMVC</a></span></p>
</footer>
</div>
</template>
<script>
import { setup, props } from '@/views/Todo.purs'
import { toProps } from 'vue-cli-plugin-pure-vue/src/Props.purs'
export default {
name: 'Todo',
props: toProps(props),
setup,
methods: {
// We can also use Javascript if we want to
addTaskJs() {
this.addTask()
this.taskTitle = ""
}
},
created() {
}
}
</script>
<style>
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all + label:before {
content: '';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked + label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li span {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.filters li span:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li span.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
</style>

View File

@@ -9,13 +9,13 @@ data PropType = Int String
| Number String | Number String
-- | Object String -- | Object String
toProp :: PropType -> String instance showPropType :: Show PropType where
toProp (Int s) = s show (Int a) = show a
toProp (String s) = s show (String a) = a
toProp (Number s) = s show (Number a) = show a
toProps :: Array PropType -> Array String toProps :: Array PropType -> Array String
toProps = map toProp toProps = map show
-- class Proppable a where -- class Proppable a where