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">
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'
@ -38,14 +38,13 @@ import auth from './auth'
import router from './router'
import store from './store'
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 { provideRouter } from './plugins/router'
import { provideStore } from './plugins/store'
// import { version } = require('../package.json')
function setupSnackbar (): ISnackbar {
function setupSnackbar (): SnackbarProps {
const events = new Vue()
const visible = ref(false)
const message = ref('')
@ -82,7 +81,7 @@ function setupSnackbar (): ISnackbar {
}
}
function setupProgress (): IProgress {
function setupProgress (): ProgressProps {
const events = new Vue()
const visible = ref(false)
const mode = ref('query')
@ -111,7 +110,7 @@ function setupProgress (): IProgress {
const SnackbarSymbol = Symbol('Snackbar events')
const ProgressSymbol = Symbol('Progress events')
export default {
export default createComponent({
name: 'app',
components: {
Navigation
@ -144,14 +143,14 @@ export default {
snackbar
}
}
}
})
export function useSnackbar () {
const snackbar = inject(SnackbarSymbol)
if (!snackbar) {
throw new Error('Snackbar not configured')
}
return snackbar as ISnackbar
return snackbar as SnackbarProps
}
export function useProgress () {
@ -159,7 +158,7 @@ export function useProgress () {
if (!progress) {
throw new Error('Progress not configured')
}
return progress as IProgress
return progress as ProgressProps
}
</script>

View File

@ -23,7 +23,7 @@ md-content(role='main').mpj-main-content-wide
<script lang="ts">
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 NotesEdit from './request/NotesEdit.vue'
@ -36,7 +36,7 @@ import { useSnackbar, useProgress } from '../App.vue'
const EventSymbol = Symbol('Journal events')
export default {
export default createComponent({
components: {
NotesEdit,
RequestCard,
@ -72,7 +72,7 @@ export default {
isLoadingJournal: store.state.isLoadingJournal
}
}
}
})
export function useEvents () {
const events = inject(EventSymbol)

View File

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

View File

@ -13,40 +13,50 @@ md-content(role='main').mpj-main-content
p(v-else) Loading answered requests...
</template>
<script>
'use strict'
<script lang="ts">
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 {
name: 'answered-requests',
inject: [
'messages',
'progress'
],
components: {
RequestList
},
data () {
return {
requests: [],
loaded: false
}
},
async mounted () {
this.progress.$emit('show', 'query')
setup () {
/** The answered requests */
let requests: JournalRequest[] = []
/** Whether the requests have been loaded */
const isLoaded = ref(false)
/** The snackbar component instance */
const snackbar = useSnackbar()
/** The progress bar instance */
const progress = useProgress()
onMounted(async () => {
progress.events.$emit('show', 'query')
try {
const reqs = await api.getAnsweredRequests()
this.requests = reqs.data
this.progress.$emit('done')
requests = reqs.data
progress.events.$emit('done')
} catch (err) {
console.error(err) // eslint-disable-line no-console
this.messages.$emit('error', 'Error loading requests; check console for details')
this.progress.$emit('done')
snackbar.events.$emit('error', 'Error loading requests; check console for details')
progress.events.$emit('done')
} 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-title Full Prayer Request
.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
md-card-content.mpj-full-page-card
p.mpj-request-text {{ lastText }}
@ -22,73 +22,83 @@ md-content(role='main').mpj-main-content
p(v-else) Loading request...
</template>
<script>
'use strict'
<script lang="ts">
import { createComponent, onMounted, computed } from '@vue/composition-api'
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 {
name: 'full-request',
inject: ['progress'],
export default createComponent({
props: {
id: {
type: String,
required: true
}
},
data () {
return {
request: null
}
},
computed: {
answered () {
return this.request.history.find(hist => hist.status === 'Answered').asOf
},
isAnswered () {
return this.request.history.filter(hist => hist.status === 'Answered').length > 0
},
lastText () {
return this.request.history
.filter(hist => hist.text)
.sort(asOfDesc)[0].text
},
log () {
const allHistory = (this.request.notes || [])
.map(note => ({ asOf: note.asOf, text: note.notes, status: 'Notes' }))
.concat(this.request.history)
setup (props) {
/** The progress bar component instance */
const progress = useProgress()
/** The request being displayed */
let request: JournalRequest = undefined
/** Whether this request is answered */
const isAnswered = computed(() => request.history.find(hist => hist.status === 'Answered'))
/** The date/time this request was answered */
const answered = computed(() => request.history.find(hist => hist.status === 'Answered').asOf)
/** The last recorded text for the request */
const lastText = computed(() => request.history.filter(hist => hist.text).sort(asOfDesc)[0].text)
/** The history log including notes (and excluding the final entry for answered requests) */
const log = computed(() => {
const allHistory = (request.notes || [])
.map(note => ({ asOf: note.asOf, text: note.notes, status: 'Notes' } as HistoryEntry))
.concat(request.history)
.sort(asOfDesc)
// Skip the first entry for answered requests; that info is already displayed
return this.isAnswered ? allHistory.slice(1) : allHistory
},
openDays () {
const asOf = this.isAnswered ? this.answered : Date.now()
return Math.floor(
(asOf - this.request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24)
},
prayedCount () {
return this.request.history.filter(hist => hist.status === 'Prayed').length
}
},
async mounted () {
this.progress.$emit('show', 'indeterminate')
return isAnswered.value ? allHistory.slice(1) : allHistory
})
/** The number of days this request [was|has been] open */
const openDays = computed(() => {
const asOf = isAnswered.value ? answered.value : Date.now()
return Math.floor((asOf - request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24)
})
/** How many times this request has been prayed for */
const prayedCount = computed(() => request.history.filter(hist => hist.status === 'Prayed').length)
/** Format a date */
const formatDate = (asOf: number) => moment(asOf).format('LL')
onMounted(async () => {
progress.events.$emit('show', 'indeterminate')
try {
const req = await api.getFullRequest(this.id)
this.request = req.data
this.progress.$emit('done')
const req = await api.getFullRequest(props.id)
request = req.data
} catch (e) {
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>

View File

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

View File

@ -29,20 +29,23 @@ md-table-row
</template>
<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 { useRouter } from '../../plugins/router'
import { useProgress, useSnackbar } from '../../App.vue'
export default {
export default createComponent({
props: {
request: { required: true }
request: {
type: JournalRequest,
required: true
}
},
setup (props, { parent }) {
/** The request to be rendered */
const request = props.request as JournalRequest
/** Shorthand for props.request */
const r = props.request
/** The Vuex store */
const store = useStore()
@ -57,43 +60,47 @@ export default {
const progress = useProgress()
/** Whether the request has been answered */
const isAnswered = computed(() => request.lastStatus === 'Answered')
const isAnswered = computed(() => r.lastStatus === 'Answered')
/** 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 */
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 */
const cancelSnooze = async () => {
const opts: ISnoozeRequestAction = {
progress: progress,
requestId: request.requestId,
const opts: SnoozeRequestAction = {
progress,
requestId: r.requestId,
until: 0
}
await store.dispatch(Actions.SnoozeRequest, opts)
snackbar.events.$emit('info', 'Request un-snoozed')
if (parent) {
parent.$emit('requestUnsnoozed')
}
}
/** 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 */
const showNow = async () => {
const opts: IShowRequestAction = {
progress: this.progress,
requestId: this.request.requestId,
const opts: ShowRequestAction = {
progress,
requestId: r.requestId,
showAfter: 0
}
await store.dispatch(Actions.ShowRequestNow, opts)
snackbar.events.$emit('info', 'Recurrence skipped; request now shows in journal')
if (parent) {
parent.$emit('requestNowShown')
}
}
/** 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 {
cancelSnooze,
@ -105,7 +112,7 @@ export default {
viewFull
}
}
}
})
</script>
<style lang="sass">

View File

@ -9,10 +9,10 @@ import {
Actions,
JournalRequest,
Mutations,
ISnoozeRequestAction,
IShowRequestAction
SnoozeRequestAction,
ShowRequestAction
} from './types'
import { IProgress } from '@/types'
import { ProgressProps } from '@/types'
Vue.use(Vuex)
@ -121,7 +121,7 @@ const store : StoreOptions<AppState> = {
commit(Mutations.SetAuthentication, false)
}
},
async [Actions.LoadJournal] ({ commit }, progress: IProgress) {
async [Actions.LoadJournal] ({ commit }, progress: ProgressProps) {
commit(Mutations.LoadedJournal, [])
progress.events.$emit('show', 'query')
commit(Mutations.LoadingJournal, true)
@ -158,7 +158,7 @@ const store : StoreOptions<AppState> = {
progress.$emit('done')
}
},
async [Actions.ShowRequestNow] ({ commit }, p: IShowRequestAction) {
async [Actions.ShowRequestNow] ({ commit }, p: ShowRequestAction) {
const { progress, requestId, showAfter } = p
progress.events.$emit('show', 'indeterminate')
try {
@ -172,7 +172,7 @@ const store : StoreOptions<AppState> = {
progress.events.$emit('done')
}
},
async [Actions.SnoozeRequest] ({ commit }, p: ISnoozeRequestAction) {
async [Actions.SnoozeRequest] ({ commit }, p: SnoozeRequestAction) {
const { progress, requestId, until } = p
progress.events.$emit('show', 'indeterminate')
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 */
export interface JournalRequest {
export class JournalRequest {
/** The ID of the request (just the CUID part) */
requestId: string
requestId = ''
/** The ID of the user to whom the request belongs */
userId: string
userId = ''
/** The current text of the request */
text: string
text = ''
/** The last time action was taken on the request */
asOf: number
asOf = 0
/** 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 */
snoozedUntil: number
snoozedUntil = 0
/** 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 */
recurType: string // TODO Recurrence union?
recurType = '' // TODO Recurrence union?
/** How many of the recurrence intervals should occur between appearances in the journal */
recurCount: number
recurCount = 0
/** History entries for the request */
history: any[] // History list
history: HistoryEntry[] = []
/** Note entries for the request */
notes: any[] // Note list
notes: any[] = [] // Note list
}
/** The state of myPrayerJournal */
@ -97,9 +109,9 @@ const mutations = {
export { mutations as Mutations }
/** The shape of the parameter to the show request action */
export interface IShowRequestAction {
export interface ShowRequestAction {
/** The progress bar component instance */
progress: IProgress
progress: ProgressProps
/** The ID of the prayer request being shown */
requestId: string
/** 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 */
export interface ISnoozeRequestAction {
export interface SnoozeRequestAction {
/** The progress bar component instance */
progress: IProgress
progress: ProgressProps
/** The ID of the prayer request being snoozed/unsnoozed */
requestId: string
/** The date/time after which the request will be once again shown */

View File

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