Version 3 #67

Merged
danieljsummers merged 53 commits from version-3 into master 2021-10-26 23:39:59 +00:00
11 changed files with 200 additions and 157 deletions
Showing only changes of commit 321ba83ab0 - Show all commits

View File

@ -0,0 +1 @@
[{"tagName":"script","closeTag":true,"attributes":{"type":"text/javascript","src":"/js/chunk-vendors-legacy.12c801f9.js"}},{"tagName":"script","closeTag":true,"attributes":{"type":"text/javascript","src":"/js/app-legacy.3c452240.js"}}]

View File

@ -30,7 +30,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import { computed, ref, onMounted, provide, inject } from '@vue/composition-api' import { computed, createComponent, inject, onMounted, provide, ref } from '@vue/composition-api'
import Navigation from '@/components/common/Navigation.vue' import Navigation from '@/components/common/Navigation.vue'
@ -38,14 +38,13 @@ import auth from './auth'
import router from './router' import router from './router'
import store from './store' import store from './store'
import { Actions } from './store/types' import { Actions } from './store/types'
import { ISnackbar, IProgress } from './types' // eslint-disable-line no-unused-vars import { SnackbarProps, ProgressProps } from './types' // eslint-disable-line no-unused-vars
import { provideAuth } from './plugins/auth' import { provideAuth } from './plugins/auth'
import { provideRouter } from './plugins/router' import { provideRouter } from './plugins/router'
import { provideStore } from './plugins/store' import { provideStore } from './plugins/store'
// import { version } = require('../package.json')
function setupSnackbar (): ISnackbar { function setupSnackbar (): SnackbarProps {
const events = new Vue() const events = new Vue()
const visible = ref(false) const visible = ref(false)
const message = ref('') const message = ref('')
@ -82,7 +81,7 @@ function setupSnackbar (): ISnackbar {
} }
} }
function setupProgress (): IProgress { function setupProgress (): ProgressProps {
const events = new Vue() const events = new Vue()
const visible = ref(false) const visible = ref(false)
const mode = ref('query') const mode = ref('query')
@ -111,7 +110,7 @@ function setupProgress (): IProgress {
const SnackbarSymbol = Symbol('Snackbar events') const SnackbarSymbol = Symbol('Snackbar events')
const ProgressSymbol = Symbol('Progress events') const ProgressSymbol = Symbol('Progress events')
export default { export default createComponent({
name: 'app', name: 'app',
components: { components: {
Navigation Navigation
@ -144,14 +143,14 @@ export default {
snackbar snackbar
} }
} }
} })
export function useSnackbar () { export function useSnackbar () {
const snackbar = inject(SnackbarSymbol) const snackbar = inject(SnackbarSymbol)
if (!snackbar) { if (!snackbar) {
throw new Error('Snackbar not configured') throw new Error('Snackbar not configured')
} }
return snackbar as ISnackbar return snackbar as SnackbarProps
} }
export function useProgress () { export function useProgress () {
@ -159,7 +158,7 @@ export function useProgress () {
if (!progress) { if (!progress) {
throw new Error('Progress not configured') throw new Error('Progress not configured')
} }
return progress as IProgress return progress as ProgressProps
} }
</script> </script>

View File

@ -23,7 +23,7 @@ md-content(role='main').mpj-main-content-wide
<script lang="ts"> <script lang="ts">
import Vue from 'vue' import Vue from 'vue'
import { computed, inject, onBeforeMount, provide } from '@vue/composition-api' import { computed, createComponent, inject, onBeforeMount, provide } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import NotesEdit from './request/NotesEdit.vue' import NotesEdit from './request/NotesEdit.vue'
@ -36,7 +36,7 @@ import { useSnackbar, useProgress } from '../App.vue'
const EventSymbol = Symbol('Journal events') const EventSymbol = Symbol('Journal events')
export default { export default createComponent({
components: { components: {
NotesEdit, NotesEdit,
RequestCard, RequestCard,
@ -72,7 +72,7 @@ export default {
isLoadingJournal: store.state.isLoadingJournal isLoadingJournal: store.state.isLoadingJournal
} }
} }
} })
export function useEvents () { export function useEvents () {
const events = inject(EventSymbol) const events = inject(EventSymbol)

View File

@ -15,17 +15,16 @@ md-content(role='main').mpj-main-content
</template> </template>
<script lang="ts"> <script lang="ts">
import { onBeforeMount, ref } from '@vue/composition-api' import { onBeforeMount, createComponent, ref } from '@vue/composition-api'
import { Store } from 'vuex' import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import RequestList from '@/components/request/RequestList.vue' import RequestList from './RequestList.vue'
import { useProgress } from '../../App.vue' import { useProgress } from '../../App.vue'
import { Actions, AppState, JournalRequest } from '../../store/types' import { Actions, AppState, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../../plugins/store' import { useStore } from '../../plugins/store'
export default { export default createComponent({
inject: ['progress'],
components: { components: {
RequestList RequestList
}, },
@ -53,14 +52,14 @@ export default {
onBeforeMount(async () => { await ensureJournal() }) onBeforeMount(async () => { await ensureJournal() })
// TODO: is "this" what we think it is here? // TODO: how do we do this?
this.$on('requestUnsnoozed', ensureJournal) // this.$on('requestUnsnoozed', ensureJournal)
this.$on('requestNowShown', ensureJournal) // this.$on('requestNowShown', ensureJournal)
return { return {
requests, requests,
isLoaded isLoaded
} }
} }
} })
</script> </script>

View File

@ -13,40 +13,50 @@ md-content(role='main').mpj-main-content
p(v-else) Loading answered requests... p(v-else) Loading answered requests...
</template> </template>
<script> <script lang="ts">
'use strict' import { ref, onMounted } from '@vue/composition-api'
import api from '@/api' import RequestList from './RequestList.vue'
import RequestList from '@/components/request/RequestList' import api from '../../api'
import { JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue'
export default { export default {
name: 'answered-requests',
inject: [
'messages',
'progress'
],
components: { components: {
RequestList RequestList
}, },
data () { setup () {
return { /** The answered requests */
requests: [], let requests: JournalRequest[] = []
loaded: false
} /** Whether the requests have been loaded */
}, const isLoaded = ref(false)
async mounted () {
this.progress.$emit('show', 'query') /** The snackbar component instance */
const snackbar = useSnackbar()
/** The progress bar instance */
const progress = useProgress()
onMounted(async () => {
progress.events.$emit('show', 'query')
try { try {
const reqs = await api.getAnsweredRequests() const reqs = await api.getAnsweredRequests()
this.requests = reqs.data requests = reqs.data
this.progress.$emit('done') progress.events.$emit('done')
} catch (err) { } catch (err) {
console.error(err) // eslint-disable-line no-console console.error(err) // eslint-disable-line no-console
this.messages.$emit('error', 'Error loading requests; check console for details') snackbar.events.$emit('error', 'Error loading requests; check console for details')
this.progress.$emit('done') progress.events.$emit('done')
} finally { } finally {
this.loaded = true isLoaded.value = true
}
})
return {
requests,
isLoaded
} }
} }
} }

View File

@ -6,7 +6,7 @@ md-content(role='main').mpj-main-content
md-card-header md-card-header
.md-title Full Prayer Request .md-title Full Prayer Request
.md-subhead .md-subhead
span(v-if='isAnswered') Answered {{ formatDate(answered) }} (#[date-from-now(:value='answered')]) !{' &bull; '} span(v-if='isAnswered.value') Answered {{ formatDate(answered) }} (#[date-from-now(:value='answered')]) !{' &bull; '}
| Prayed {{ prayedCount }} times &bull; Open {{ openDays }} days | Prayed {{ prayedCount }} times &bull; Open {{ openDays }} days
md-card-content.mpj-full-page-card md-card-content.mpj-full-page-card
p.mpj-request-text {{ lastText }} p.mpj-request-text {{ lastText }}
@ -22,73 +22,83 @@ md-content(role='main').mpj-main-content
p(v-else) Loading request... p(v-else) Loading request...
</template> </template>
<script> <script lang="ts">
'use strict' import { createComponent, onMounted, computed } from '@vue/composition-api'
import moment from 'moment' import moment from 'moment'
import api from '@/api' import api from '../../api'
import { useProgress } from '../../App.vue'
import { HistoryEntry, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
const asOfDesc = (a, b) => b.asOf - a.asOf /** Sort history entries in descending order */
const asOfDesc = (a: HistoryEntry, b: HistoryEntry) => b.asOf - a.asOf
export default { export default createComponent({
name: 'full-request',
inject: ['progress'],
props: { props: {
id: { id: {
type: String, type: String,
required: true required: true
} }
}, },
data () { setup (props) {
return { /** The progress bar component instance */
request: null const progress = useProgress()
}
}, /** The request being displayed */
computed: { let request: JournalRequest = undefined
answered () {
return this.request.history.find(hist => hist.status === 'Answered').asOf /** Whether this request is answered */
}, const isAnswered = computed(() => request.history.find(hist => hist.status === 'Answered'))
isAnswered () {
return this.request.history.filter(hist => hist.status === 'Answered').length > 0 /** The date/time this request was answered */
}, const answered = computed(() => request.history.find(hist => hist.status === 'Answered').asOf)
lastText () {
return this.request.history /** The last recorded text for the request */
.filter(hist => hist.text) const lastText = computed(() => request.history.filter(hist => hist.text).sort(asOfDesc)[0].text)
.sort(asOfDesc)[0].text
}, /** The history log including notes (and excluding the final entry for answered requests) */
log () { const log = computed(() => {
const allHistory = (this.request.notes || []) const allHistory = (request.notes || [])
.map(note => ({ asOf: note.asOf, text: note.notes, status: 'Notes' })) .map(note => ({ asOf: note.asOf, text: note.notes, status: 'Notes' } as HistoryEntry))
.concat(this.request.history) .concat(request.history)
.sort(asOfDesc) .sort(asOfDesc)
// Skip the first entry for answered requests; that info is already displayed // Skip the first entry for answered requests; that info is already displayed
return this.isAnswered ? allHistory.slice(1) : allHistory return isAnswered.value ? allHistory.slice(1) : allHistory
}, })
openDays () {
const asOf = this.isAnswered ? this.answered : Date.now() /** The number of days this request [was|has been] open */
return Math.floor( const openDays = computed(() => {
(asOf - this.request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24) const asOf = isAnswered.value ? answered.value : Date.now()
}, return Math.floor((asOf - request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24)
prayedCount () { })
return this.request.history.filter(hist => hist.status === 'Prayed').length
} /** How many times this request has been prayed for */
}, const prayedCount = computed(() => request.history.filter(hist => hist.status === 'Prayed').length)
async mounted () {
this.progress.$emit('show', 'indeterminate') /** Format a date */
const formatDate = (asOf: number) => moment(asOf).format('LL')
onMounted(async () => {
progress.events.$emit('show', 'indeterminate')
try { try {
const req = await api.getFullRequest(this.id) const req = await api.getFullRequest(props.id)
this.request = req.data request = req.data
this.progress.$emit('done')
} catch (e) { } catch (e) {
console.log(e) // eslint-disable-line no-console console.log(e) // eslint-disable-line no-console
this.progress.$emit('done') } finally {
} progress.events.$emit('done')
},
methods: {
formatDate (asOf) {
return moment(asOf).format('LL')
} }
})
return {
answered,
formatDate,
isAnswered,
lastText,
log,
openDays,
prayedCount,
request
} }
} }
})
</script> </script>

View File

@ -11,9 +11,13 @@ md-table(md-card)
</template> </template>
<script lang="ts"> <script lang="ts">
import { createComponent } from '@vue/composition-api'
import RequestListItem from './RequestListItem.vue' import RequestListItem from './RequestListItem.vue'
export default { import { JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
export default createComponent({
components: { RequestListItem }, components: { RequestListItem },
props: { props: {
title: { title: {
@ -26,12 +30,13 @@ export default {
} }
}, },
setup (props, { parent }) { setup (props, { parent }) {
this.$on('requestUnsnoozed', parent.$emit('requestUnsnoozed')) // TODO: custom events
this.$on('requestNowShown', parent.$emit('requestNowShown')) // this.$on('requestUnsnoozed', parent.$emit('requestUnsnoozed'))
// this.$on('requestNowShown', parent.$emit('requestNowShown'))
return { return {
title: props.title, title: props.title,
requests: props.requests requests: props.requests as JournalRequest[]
} }
},
} }
})
</script> </script>

View File

@ -29,20 +29,23 @@ md-table-row
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed } from '@vue/composition-api' import { computed, createComponent } from '@vue/composition-api'
import { Actions, JournalRequest, ISnoozeRequestAction, IShowRequestAction } from '../../store/types' import { Actions, JournalRequest, SnoozeRequestAction, ShowRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../../plugins/store' import { useStore } from '../../plugins/store'
import { useRouter } from '../../plugins/router' import { useRouter } from '../../plugins/router'
import { useProgress, useSnackbar } from '../../App.vue' import { useProgress, useSnackbar } from '../../App.vue'
export default { export default createComponent({
props: { props: {
request: { required: true } request: {
type: JournalRequest,
required: true
}
}, },
setup (props, { parent }) { setup (props, { parent }) {
/** The request to be rendered */ /** Shorthand for props.request */
const request = props.request as JournalRequest const r = props.request
/** The Vuex store */ /** The Vuex store */
const store = useStore() const store = useStore()
@ -57,43 +60,47 @@ export default {
const progress = useProgress() const progress = useProgress()
/** Whether the request has been answered */ /** Whether the request has been answered */
const isAnswered = computed(() => request.lastStatus === 'Answered') const isAnswered = computed(() => r.lastStatus === 'Answered')
/** Whether the request is snoozed */ /** Whether the request is snoozed */
const isSnoozed = computed(() => request.snoozedUntil > Date.now()) const isSnoozed = computed(() => r.snoozedUntil > Date.now())
/** Whether the request is not shown because of an interval */ /** Whether the request is not shown because of an interval */
const isPending = computed(() => !isSnoozed.value && request.showAfter > Date.now()) const isPending = computed(() => !isSnoozed.value && r.showAfter > Date.now())
/** Cancel the snooze period for this request */ /** Cancel the snooze period for this request */
const cancelSnooze = async () => { const cancelSnooze = async () => {
const opts: ISnoozeRequestAction = { const opts: SnoozeRequestAction = {
progress: progress, progress,
requestId: request.requestId, requestId: r.requestId,
until: 0 until: 0
} }
await store.dispatch(Actions.SnoozeRequest, opts) await store.dispatch(Actions.SnoozeRequest, opts)
snackbar.events.$emit('info', 'Request un-snoozed') snackbar.events.$emit('info', 'Request un-snoozed')
if (parent) {
parent.$emit('requestUnsnoozed') parent.$emit('requestUnsnoozed')
} }
}
/** Edit the given request */ /** Edit the given request */
const editRequest = () => { router.push({ name: 'EditRequest', params: { id: request.requestId } }) } const editRequest = () => { router.push({ name: 'EditRequest', params: { id: r.requestId } }) }
/** Show the request now */ /** Show the request now */
const showNow = async () => { const showNow = async () => {
const opts: IShowRequestAction = { const opts: ShowRequestAction = {
progress: this.progress, progress,
requestId: this.request.requestId, requestId: r.requestId,
showAfter: 0 showAfter: 0
} }
await store.dispatch(Actions.ShowRequestNow, opts) await store.dispatch(Actions.ShowRequestNow, opts)
snackbar.events.$emit('info', 'Recurrence skipped; request now shows in journal') snackbar.events.$emit('info', 'Recurrence skipped; request now shows in journal')
if (parent) {
parent.$emit('requestNowShown') parent.$emit('requestNowShown')
} }
}
/** View the full request */ /** View the full request */
const viewFull = () => { router.push({ name: 'FullRequest', params: { id: request.requestId } }) } const viewFull = () => { router.push({ name: 'FullRequest', params: { id: r.requestId } }) }
return { return {
cancelSnooze, cancelSnooze,
@ -105,7 +112,7 @@ export default {
viewFull viewFull
} }
} }
} })
</script> </script>
<style lang="sass"> <style lang="sass">

View File

@ -9,10 +9,10 @@ import {
Actions, Actions,
JournalRequest, JournalRequest,
Mutations, Mutations,
ISnoozeRequestAction, SnoozeRequestAction,
IShowRequestAction ShowRequestAction
} from './types' } from './types'
import { IProgress } from '@/types' import { ProgressProps } from '@/types'
Vue.use(Vuex) Vue.use(Vuex)
@ -121,7 +121,7 @@ const store : StoreOptions<AppState> = {
commit(Mutations.SetAuthentication, false) commit(Mutations.SetAuthentication, false)
} }
}, },
async [Actions.LoadJournal] ({ commit }, progress: IProgress) { async [Actions.LoadJournal] ({ commit }, progress: ProgressProps) {
commit(Mutations.LoadedJournal, []) commit(Mutations.LoadedJournal, [])
progress.events.$emit('show', 'query') progress.events.$emit('show', 'query')
commit(Mutations.LoadingJournal, true) commit(Mutations.LoadingJournal, true)
@ -158,7 +158,7 @@ const store : StoreOptions<AppState> = {
progress.$emit('done') progress.$emit('done')
} }
}, },
async [Actions.ShowRequestNow] ({ commit }, p: IShowRequestAction) { async [Actions.ShowRequestNow] ({ commit }, p: ShowRequestAction) {
const { progress, requestId, showAfter } = p const { progress, requestId, showAfter } = p
progress.events.$emit('show', 'indeterminate') progress.events.$emit('show', 'indeterminate')
try { try {
@ -172,7 +172,7 @@ const store : StoreOptions<AppState> = {
progress.events.$emit('done') progress.events.$emit('done')
} }
}, },
async [Actions.SnoozeRequest] ({ commit }, p: ISnoozeRequestAction) { async [Actions.SnoozeRequest] ({ commit }, p: SnoozeRequestAction) {
const { progress, requestId, until } = p const { progress, requestId, until } = p
progress.events.$emit('show', 'indeterminate') progress.events.$emit('show', 'indeterminate')
try { try {

View File

@ -1,39 +1,51 @@
import { IProgress } from '@/types' import { ProgressProps } from '@/types'
/** A history entry for a prayer request */
export class HistoryEntry {
/** The status for this history entry */
status = '' // TODO string union?
/** The date/time for this history entry */
asOf = 0
/** The text of this history entry */
text?: string = undefined
}
/** A prayer request that is part of the user's journal */ /** A prayer request that is part of the user's journal */
export interface JournalRequest { export class JournalRequest {
/** The ID of the request (just the CUID part) */ /** The ID of the request (just the CUID part) */
requestId: string requestId = ''
/** The ID of the user to whom the request belongs */ /** The ID of the user to whom the request belongs */
userId: string userId = ''
/** The current text of the request */ /** The current text of the request */
text: string text = ''
/** The last time action was taken on the request */ /** The last time action was taken on the request */
asOf: number asOf = 0
/** The last status for the request */ /** The last status for the request */
lastStatus: string // TODO string union? lastStatus = '' // TODO string union?
/** The time that this request should reappear in the user's journal */ /** The time that this request should reappear in the user's journal */
snoozedUntil: number snoozedUntil = 0
/** The time after which this request should reappear in the user's journal by configured recurrence */ /** The time after which this request should reappear in the user's journal by configured recurrence */
showAfter: number showAfter = 0
/** The type of recurrence for this request */ /** The type of recurrence for this request */
recurType: string // TODO Recurrence union? recurType = '' // TODO Recurrence union?
/** How many of the recurrence intervals should occur between appearances in the journal */ /** How many of the recurrence intervals should occur between appearances in the journal */
recurCount: number recurCount = 0
/** History entries for the request */ /** History entries for the request */
history: any[] // History list history: HistoryEntry[] = []
/** Note entries for the request */ /** Note entries for the request */
notes: any[] // Note list notes: any[] = [] // Note list
} }
/** The state of myPrayerJournal */ /** The state of myPrayerJournal */
@ -97,9 +109,9 @@ const mutations = {
export { mutations as Mutations } export { mutations as Mutations }
/** The shape of the parameter to the show request action */ /** The shape of the parameter to the show request action */
export interface IShowRequestAction { export interface ShowRequestAction {
/** The progress bar component instance */ /** The progress bar component instance */
progress: IProgress progress: ProgressProps
/** The ID of the prayer request being shown */ /** The ID of the prayer request being shown */
requestId: string requestId: string
/** The date/time after which the request will be once again shown */ /** The date/time after which the request will be once again shown */
@ -107,9 +119,9 @@ export interface IShowRequestAction {
} }
/** The shape of the parameter to the snooze request action */ /** The shape of the parameter to the snooze request action */
export interface ISnoozeRequestAction { export interface SnoozeRequestAction {
/** The progress bar component instance */ /** The progress bar component instance */
progress: IProgress progress: ProgressProps
/** The ID of the prayer request being snoozed/unsnoozed */ /** The ID of the prayer request being snoozed/unsnoozed */
requestId: string requestId: string
/** The date/time after which the request will be once again shown */ /** The date/time after which the request will be once again shown */

View File

@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import { Ref } from '@vue/composition-api'; import { Ref } from '@vue/composition-api';
export interface ISnackbar { export interface SnackbarProps {
events: Vue events: Vue
visible: Ref<boolean> visible: Ref<boolean>
message: Ref<string> message: Ref<string>
@ -11,7 +11,7 @@ export interface ISnackbar {
showError: (msg: string) => void showError: (msg: string) => void
} }
export interface IProgress { export interface ProgressProps {
events: Vue events: Vue
visible: Ref<boolean> visible: Ref<boolean>
mode: Ref<string> mode: Ref<string>