Updated compose; added smooth transitions

This commit is contained in:
Dan Elbert 2019-11-10 10:40:26 -06:00
parent eeb8e84344
commit ccb87d7019
14 changed files with 215 additions and 118 deletions

View File

@ -8,7 +8,7 @@ gem 'bootsnap', '>= 1.1.0', require: false
gem 'jbuilder', '~> 2.9' gem 'jbuilder', '~> 2.9'
gem 'oj', '~> 3.9.1' gem 'oj', '~> 3.9.2'
gem 'kaminari', '~> 1.1.1' gem 'kaminari', '~> 1.1.1'
gem 'unitwise', '~> 2.2.0' gem 'unitwise', '~> 2.2.0'
@ -23,7 +23,7 @@ gem 'tzinfo-data'
group :development, :test do group :development, :test do
gem 'puma', '~> 4.1.1' gem 'puma', '~> 4.3.0'
gem 'sqlite3', '~> 1.4.1' gem 'sqlite3', '~> 1.4.1'
gem 'guard', '~> 2.15.0' gem 'guard', '~> 2.15.0'

View File

@ -49,11 +49,11 @@ GEM
builder (3.2.3) builder (3.2.3)
coderay (1.1.2) coderay (1.1.2)
concurrent-ruby (1.1.5) concurrent-ruby (1.1.5)
crass (1.0.4) crass (1.0.5)
dalli (2.7.10) dalli (2.7.10)
database_cleaner (1.7.0) database_cleaner (1.7.0)
diff-lcs (1.3) diff-lcs (1.3)
erubi (1.8.0) erubi (1.9.0)
factory_bot (5.0.2) factory_bot (5.0.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
factory_bot_rails (5.0.2) factory_bot_rails (5.0.2)
@ -77,7 +77,7 @@ GEM
guard (~> 2.1) guard (~> 2.1)
guard-compat (~> 1.1) guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0) rspec (>= 2.99.0, < 4.0)
i18n (1.6.0) i18n (1.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jbuilder (2.9.1) jbuilder (2.9.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -94,11 +94,10 @@ GEM
kaminari-core (= 1.1.1) kaminari-core (= 1.1.1)
kaminari-core (1.1.1) kaminari-core (1.1.1)
liner (0.2.4) liner (0.2.4)
listen (3.1.5) listen (3.2.0)
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.10)
ruby_dep (~> 1.2) loofah (2.3.1)
loofah (2.2.3)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.0.13) lumberjack (1.0.13)
@ -112,22 +111,22 @@ GEM
mimemagic (0.3.3) mimemagic (0.3.3)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.11.3) minitest (5.13.0)
msgpack (1.3.1) msgpack (1.3.1)
nenv (0.3.0) nenv (0.3.0)
nio4r (2.5.1) nio4r (2.5.2)
nokogiri (1.10.4) nokogiri (1.10.5)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
notiffany (0.1.3) notiffany (0.1.3)
nenv (~> 0.1) nenv (~> 0.1)
shellany (~> 0.0) shellany (~> 0.0)
oj (3.9.1) oj (3.9.2)
parslet (1.8.2) parslet (1.8.2)
pg (1.1.4) pg (1.1.4)
pry (0.12.2) pry (0.12.2)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
puma (4.1.1) puma (4.3.0)
nio4r (~> 2.0) nio4r (~> 2.0)
rack (2.0.7) rack (2.0.7)
rack-proxy (0.6.5) rack-proxy (0.6.5)
@ -154,15 +153,15 @@ GEM
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.2.0) rails-html-sanitizer (1.3.0)
loofah (~> 2.2, >= 2.2.2) loofah (~> 2.3)
railties (5.2.3) railties (5.2.3)
actionpack (= 5.2.3) actionpack (= 5.2.3)
activesupport (= 5.2.3) activesupport (= 5.2.3)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0) thor (>= 0.19.0, < 2.0)
rake (12.3.3) rake (13.0.0)
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.0) rb-inotify (0.10.0)
ffi (~> 1.0) ffi (~> 1.0)
@ -173,13 +172,13 @@ GEM
rspec-mocks (~> 3.8.0) rspec-mocks (~> 3.8.0)
rspec-core (3.8.2) rspec-core (3.8.2)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-expectations (3.8.4) rspec-expectations (3.8.6)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-mocks (3.8.1) rspec-mocks (3.8.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-rails (3.8.2) rspec-rails (3.8.3)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
@ -187,11 +186,10 @@ GEM
rspec-expectations (~> 3.8.0) rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0) rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-support (3.8.2) rspec-support (3.8.3)
ruby_dep (1.5.0)
shellany (0.0.1) shellany (0.0.1)
signed_multiset (0.2.1) signed_multiset (0.2.1)
sprockets (3.7.2) sprockets (4.0.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.1) sprockets-rails (3.2.1)
@ -231,9 +229,9 @@ DEPENDENCIES
guard-rspec guard-rspec
jbuilder (~> 2.9) jbuilder (~> 2.9)
kaminari (~> 1.1.1) kaminari (~> 1.1.1)
oj (~> 3.9.1) oj (~> 3.9.2)
pg (~> 1.1.4) pg (~> 1.1.4)
puma (~> 4.1.1) puma (~> 4.3.0)
rails (= 5.2.3) rails (= 5.2.3)
rails-controller-testing rails-controller-testing
redcarpet (~> 3.5.0) redcarpet (~> 3.5.0)

View File

@ -1,14 +1,16 @@
<template> <template>
<div> <div id="app">
<vue-progress-bar></vue-progress-bar> <vue-progress-bar></vue-progress-bar>
<app-navbar></app-navbar> <app-navbar></app-navbar>
<section id="app" class=""> <section id="main" class="">
<div class="container"> <div class="container">
<router-view v-if="!hasError"></router-view> <transition name="fade" mode="out-in">
<div v-else> <router-view v-if="!hasError"></router-view>
<h1>Error!</h1> <div v-else>
<p>{{error}}</p> <h1>Error!</h1>
</div> <p>{{error}}</p>
</div>
</transition>
</div> </div>
</section> </section>
</div> </div>
@ -30,7 +32,8 @@
...mapState({ ...mapState({
hasError: state => state.error !== null, hasError: state => state.error !== null,
error: state => state.error, error: state => state.error,
authChecked: state => state.authChecked authChecked: state => state.authChecked,
initialLoad: state => state.initialLoad
}) })
}, },
@ -41,6 +44,14 @@
} else { } else {
this.$Progress.finish(); this.$Progress.finish();
} }
},
initialLoad(val) {
if (val) {
this.$nextTick(() => {
document.body.classList.remove("loading");
});
}
} }
}, },
@ -57,6 +68,14 @@
} }
}, },
mounted() {
if (this.initialLoad) {
this.$nextTick(() => {
document.body.classList.remove("loading");
});
}
},
components: { components: {
} }
} }

View File

@ -0,0 +1,20 @@
<template>
<div class="app-loading">
Imagine I'm a spinner...
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
.app-loading {
position: absolute;
display: none;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<nav v-show="totalPages > 1" class="pagination" role="navigation" :aria-label="pagedItemName + ' page navigation'"> <nav v-show="totalPages > 1 || showWithSinglePage" class="pagination" role="navigation" :aria-label="pagedItemName + ' page navigation'">
<a class="pagination-previous" :title="isFirstPage ? 'This is the first page' : ''" :disabled="isFirstPage" @click.prevent="changePage(currentPage - 1)">Previous</a> <a class="pagination-previous" :title="isFirstPage ? 'This is the first page' : ''" :disabled="isFirstPage" @click.prevent="changePage(currentPage - 1)">Previous</a>
<a class="pagination-next" :title="isLastPage ? 'This is the last page' : ''" :disabled="isLastPage" @click.prevent="changePage(currentPage + 1)">Next page</a> <a class="pagination-next" :title="isLastPage ? 'This is the last page' : ''" :disabled="isLastPage" @click.prevent="changePage(currentPage + 1)">Next page</a>
<ul class="pagination-list"> <ul class="pagination-list">
@ -41,6 +41,12 @@
required: false, required: false,
type: Number, type: Number,
default: 1 default: 1
},
showWithSinglePage: {
required: false,
type: Boolean,
default: true
} }
}, },

View File

@ -28,7 +28,7 @@
<th colspan="4"></th> <th colspan="4"></th>
</tr> </tr>
</thead> </thead>
<tbody> <transition-group tag="tbody" name="fade" mode="out-in">
<tr v-for="i in foods" :key="i.id"> <tr v-for="i in foods" :key="i.id">
<td><router-link :to="{name: 'food', params: { id: i.id } }">{{i.name}}</router-link></td> <td><router-link :to="{name: 'food', params: { id: i.id } }">{{i.name}}</router-link></td>
<td><app-icon v-if="i.usda" icon="check"></app-icon></td> <td><app-icon v-if="i.usda" icon="check"></app-icon></td>
@ -43,7 +43,7 @@
</button> </button>
</td> </td>
</tr> </tr>
</tbody> </transition-group>
</table> </table>
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="food" @changePage="changePage"></app-pager> <app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="food" @changePage="changePage"></app-pager>

View File

@ -6,83 +6,87 @@
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager> <app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager>
<app-loading v-if="localLoading"></app-loading>
<table class="table is-fullwidth" :class="{ small: mediaQueries.touch }"> <table class="table is-fullwidth" :class="{ small: mediaQueries.touch }">
<thead> <thead>
<tr> <tr>
<th v-for="h in tableHeader" :key="h.name"> <th v-for="h in tableHeader" :key="h.name">
<a v-if="h.sort" href="#" @click.prevent="setSort(h.name)"> <a v-if="h.sort" href="#" @click.prevent="setSort(h.name)">
{{h.label}} {{h.label}}
<app-icon v-if="search.sortColumn === h.name" size="sm" :icon="search.sortDirection === 'asc' ? 'caret-bottom' : 'caret-top'"></app-icon> <app-icon v-if="search.sortColumn === h.name" size="sm" :icon="search.sortDirection === 'asc' ? 'caret-bottom' : 'caret-top'"></app-icon>
</a> </a>
<span v-else>{{h.label}}</span> <span v-else>{{h.label}}</span>
</th> </th>
<th></th> <th></th>
</tr> </tr>
<tr> <tr>
<td> <td>
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input type="text" class="input" placeholder="search names" v-model="search.name"> <input type="text" class="input" placeholder="search names" v-model="search.name">
</div>
</div> </div>
</td> </div>
<td> </td>
<div class="field"> <td>
<div class="control"> <div class="field">
<input type="text" class="input" placeholder="search tags" v-model="search.tags"> <div class="control">
</div> <input type="text" class="input" placeholder="search tags" v-model="search.tags">
</div> </div>
</td> </div>
<td colspan="5"></td> </td>
</tr> <td colspan="5"></td>
</tr>
</thead> </thead>
<tbody> <transition-group name="fade" tag="tbody">
<tr v-for="r in recipes" :key="r.id"> <tr v-for="r in recipes" :key="r.id">
<td><router-link :to="{name: 'recipe', params: { id: r.id } }">{{r.name}}</router-link></td> <td><router-link :to="{name: 'recipe', params: { id: r.id } }">{{r.name}}</router-link></td>
<td> <td>
<div class="tags"> <div class="tags">
<span class="tag" v-for="tag in r.tags" :key="tag">{{tag}}</span> <span class="tag" v-for="tag in r.tags" :key="tag">{{tag}}</span>
</div>
</td>
<td>
<app-rating v-if="r.rating !== null" :value="r.rating" readonly></app-rating>
<span v-else>--</span>
</td>
<td>{{ r.yields }}</td>
<td class="recipe-time">{{ formatRecipeTime(r.total_time, r.active_time) }}</td>
<td><app-date-time :date-time="r.created_at" :show-time="false"></app-date-time></td>
<td>
<app-dropdown hover v-if="isLoggedIn" class="is-right">
<button slot="button" class="button">
<app-icon icon="menu"></app-icon>
</button>
<div class="dropdown-item">
<router-link :to="{name: 'new_log', params: { recipeId: r.id } }" class="button is-primary is-fullwidth">
<app-icon icon="star" size="md"></app-icon> <span>Add Log Entry</span>
</router-link>
</div> </div>
</td>
<td> <div class="dropdown-item">
<app-rating v-if="r.rating !== null" :value="r.rating" readonly></app-rating> <router-link :to="{name: 'edit_recipe', params: { id: r.id } }" class="button is-primary is-fullwidth">
<span v-else>--</span> <app-icon icon="pencil" size="md"></app-icon> <span>Edit Recipe</span>
</td> </router-link>
<td>{{ r.yields }}</td> </div>
<td class="recipe-time">{{ formatRecipeTime(r.total_time, r.active_time) }}</td>
<td><app-date-time :date-time="r.created_at" :show-time="false"></app-date-time></td> <div class="dropdown-item">
<td> <button type="button" class="button is-danger is-fullwidth" @click="deleteRecipe(r)">
<app-dropdown hover v-if="isLoggedIn" class="is-right"> <app-icon icon="x" size="md"></app-icon> <span>Delete Recipe</span>
<button slot="button" class="button">
<app-icon icon="menu"></app-icon>
</button> </button>
</div>
<div class="dropdown-item"> </app-dropdown>
<router-link :to="{name: 'new_log', params: { recipeId: r.id } }" class="button is-primary is-fullwidth"> </td>
<app-icon icon="star" size="md"></app-icon> <span>Add Log Entry</span> </tr>
</router-link> </transition-group>
</div>
<div class="dropdown-item">
<router-link :to="{name: 'edit_recipe', params: { id: r.id } }" class="button is-primary is-fullwidth">
<app-icon icon="pencil" size="md"></app-icon> <span>Edit Recipe</span>
</router-link>
</div>
<div class="dropdown-item">
<button type="button" class="button is-danger is-fullwidth" @click="deleteRecipe(r)">
<app-icon icon="x" size="md"></app-icon> <span>Delete Recipe</span>
</button>
</div>
</app-dropdown>
</td>
</tr>
</tbody>
</table> </table>
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager> <app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager>
<app-confirm :open="showConfirmRecipeDelete" :message="confirmRecipeDeleteMessage" :cancel="recipeDeleteCancel" :confirm="recipeDeleteConfirm"></app-confirm> <app-confirm :open="showConfirmRecipeDelete" :message="confirmRecipeDeleteMessage" :cancel="recipeDeleteCancel" :confirm="recipeDeleteConfirm"></app-confirm>
</div> </div>
@ -92,7 +96,8 @@
import api from "../lib/Api"; import api from "../lib/Api";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { mapState } from "vuex"; import { mapMutations, mapState } from "vuex";
import AppLoading from "./AppLoading";
export default { export default {
data() { data() {
@ -161,6 +166,10 @@
}, },
methods: { methods: {
...mapMutations([
"setInitialLoad"
]),
changePage(idx) { changePage(idx) {
this.search.page = idx; this.search.page = idx;
}, },
@ -220,7 +229,7 @@
created() { created() {
this.$watch("search", this.$watch("search",
() => this.getList(), () => this.getList().then(() => this.setInitialLoad(true)),
{ {
deep: true, deep: true,
immediate: true immediate: true
@ -229,6 +238,7 @@
}, },
components: { components: {
AppLoading
} }
} }

View File

@ -80,4 +80,5 @@ document.addEventListener('DOMContentLoaded', () => {
}, },
components: { App } components: { App }
}); });
}); });

View File

@ -28,12 +28,20 @@ import TheUserEditor from './components/TheUserEditor';
import TheAdminUserList from './components/TheAdminUserList'; import TheAdminUserList from './components/TheAdminUserList';
import TheAdminUserEditor from './components/TheAdminUserEditor'; import TheAdminUserEditor from './components/TheAdminUserEditor';
import $store from './store';
Vue.use(Router); Vue.use(Router);
const router = new Router({ const router = new Router({
routes: [] routes: []
}); });
router.afterEach((to, from) => {
if (to.meta.handleInitialLoad !== true && $store.state.initialLoad === false) {
$store.commit("setInitialLoad", true);
}
});
router.addRoutes( router.addRoutes(
[ [
{ {
@ -43,7 +51,10 @@ router.addRoutes(
{ {
path: '/recipes', path: '/recipes',
name: 'recipeList', name: 'recipeList',
component: TheRecipeList component: TheRecipeList,
meta: {
handleInitialLoad: true
}
}, },
{ {
path: '/recipes/new', path: '/recipes/new',

View File

@ -8,6 +8,7 @@ Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production', strict: process.env.NODE_ENV !== 'production',
state: { state: {
initialLoad: false,
updateAvailable: false, updateAvailable: false,
loadingCount: 0, loadingCount: 0,
error: null, error: null,
@ -76,6 +77,10 @@ export default new Vuex.Store({
state.updateAvailable = value; state.updateAvailable = value;
}, },
setInitialLoad(state, value) {
state.initialLoad = value;
},
setLoading(state, value) { setLoading(state, value) {
if (value) { if (value) {
state.loadingCount = state.loadingCount + 1; state.loadingCount = state.loadingCount + 1;

View File

@ -1,11 +1,22 @@
.fade-enter-active, .fade-leave-active { .fade-enter-active, .fade-leave-active {
transition: opacity .5s, max-height .5s; //max-height: 300px;
max-height: 300px;
} }
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0; opacity: 0;
max-height: 0; //max-height: 0;
}
.fade-leave-active {
transition: all 0.125s;
}
.fade-enter-active {
transition: all 0.75s;
}
.fade-move {
//transition: transform 1s;
} }

View File

@ -32,7 +32,7 @@ body {
padding-bottom: 2rem; padding-bottom: 2rem;
} }
#app { #main {
padding-top: 1rem; padding-top: 1rem;
.container { .container {

View File

@ -7,7 +7,7 @@
<meta name ="theme-color" content="#79A736"> <meta name ="theme-color" content="#79A736">
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<!-- generics --> <%# generics %>
<link rel="icon" href="/logo_32.png" sizes="32x32"> <link rel="icon" href="/logo_32.png" sizes="32x32">
<link rel="icon" href="/logo_57.png" sizes="57x57"> <link rel="icon" href="/logo_57.png" sizes="57x57">
<link rel="icon" href="/logo_76.png" sizes="76x76"> <link rel="icon" href="/logo_76.png" sizes="76x76">
@ -16,27 +16,43 @@
<link rel="icon" href="/logo_192.png" sizes="192x192"> <link rel="icon" href="/logo_192.png" sizes="192x192">
<link rel="icon" href="/logo_228.png" sizes="228x228"> <link rel="icon" href="/logo_228.png" sizes="228x228">
<!-- Android --> <%# Android %>
<link rel="shortcut icon" sizes="196x196" href="/logo_196.png"> <link rel="shortcut icon" sizes="196x196" href="/logo_196.png">
<!-- iOS --> <%# iOS %>
<link rel="apple-touch-icon" href="/logo_120.png" sizes="120x120"> <link rel="apple-touch-icon" href="/logo_120.png" sizes="120x120">
<link rel="apple-touch-icon" href="/logo_152.png" sizes="152x152"> <link rel="apple-touch-icon" href="/logo_152.png" sizes="152x152">
<link rel="apple-touch-icon" href="/logo_180.png" sizes="180x180"> <link rel="apple-touch-icon" href="/logo_180.png" sizes="180x180">
<!-- Windows 8 IE 10--> <%# Windows 8 IE 10 %>
<meta name="msapplication-TileColor" content="#4a4a4a"> <meta name="msapplication-TileColor" content="#4a4a4a">
<meta name="msapplication-TileImage" content="/logo_144.png"> <meta name="msapplication-TileImage" content="/logo_144.png">
<!— Windows 8.1 + IE11 and above —> <%# Windows 8.1 + IE11 and above %>
<meta name="msapplication-config" content="/browserconfig.xml" /> <meta name="msapplication-config" content="/browserconfig.xml" />
<title>Parsley</title> <title>Parsley</title>
<style type="text/css">
body.loading {
background-color: #79A736;
}
#app {
transition: opacity 0.5s ease-in;
}
body.loading #app {
opacity: 0;
}
</style>
<%= stylesheet_pack_tag 'application' %> <%= stylesheet_pack_tag 'application' %>
</head> </head>
<body> <body class="loading">
<div id="app" data-url="<%= root_url %>"> <div id="app" data-url="<%= root_url %>">
<div id="app-placeholder"> <div id="app-placeholder">

View File

@ -42,8 +42,8 @@ self.addEventListener('fetch', function(event) {
var isCacheThenNetwork = event.request.headers.get("Cache-Then-Network") === "true"; var isCacheThenNetwork = event.request.headers.get("Cache-Then-Network") === "true";
var x, asset; var x, asset;
// Any non-GET or non-http(s) request should be ignored // Any non-GET or non-http(s) or dev server request should be ignored
if (event.request.method !== 'GET' || event.request.url.indexOf('http') !== 0) { if (event.request.method !== 'GET' || event.request.url.indexOf('http') !== 0 || reqUrl.pathname.indexOf("/sockjs-node") === 0) {
return; return;
} }