Added a lot of routes and views

This commit is contained in:
Vivek Santayana 2022-08-31 00:53:25 +01:00
parent 152a199ee2
commit 7d4d6b354c
16 changed files with 881 additions and 115 deletions

View File

@ -1,11 +1,20 @@
import { createRouter, createWebHistory } from 'vue-router'
import { mdiAccountGroupOutline, mdiAccountQuestion, mdiCodeTags, mdiHandCoin, mdiHome, mdiInformation } from '@mdi/js'
import { mdiAccountBoxMultiple, mdiAccountGroupOutline, mdiAccountQuestion, mdiChartBox, mdiCodeTags, mdiCounter, mdiEarth, mdiHandCoin, mdiHome, mdiInformation, mdiWeb } from '@mdi/js'
import About from '@/views/about/Index.vue'
import AboutPage from '@/views/about/About.vue'
import Acknowledgements from '@/views/about/Acknowledgements.vue'
import Wanderhome from '@/views/about/Wanderhome.vue'
import Home from '@/views/Home.vue'
import Quiz from '@/views/Quiz.vue'
import Quiz from '@/views/quiz/Index.vue'
import QuizPage from '@/views/quiz/Quiz.vue'
import Question from '@/views/quiz/Question.vue'
import Error404 from '@/views/errors/Error404.vue'
import Refused from '@/views/errors/Refused.vue'
import Results from '@/views/results/Index.vue'
import ResultsPage from '@/views/results/Results.vue'
import Scores from '@/views/results/Scores.vue'
import Answers from '@/views/results/Answers.vue'
import Statistics from '@/views/results/Statistics.vue'
const routes = [
{
@ -26,8 +35,33 @@ const routes = [
index: 1,
svgPath: mdiAccountQuestion,
title: 'Take the Quiz'
},
children: [
{
path: '',
name: 'Quiz',
component: QuizPage,
meta: {
index: 0,
parentIndex: 1,
title: 'Take the Quiz'
}
},
{
path: 'question/:id',
name: 'Question',
component: Question,
props: route => ({
id: parseInt(route.params.id)
}),
meta: {
index: 1,
parentIndex: 1,
title: 'Take the Quiz'
}
}
]
},
{
path: '/about',
name: 'About',
@ -82,6 +116,92 @@ const routes = [
}
}
]
},
{
path: '/results',
name: 'Results',
component: Results,
meta: {
index: -1,
title: 'Results',
svgPath: mdiAccountBoxMultiple
},
children: [
{
path: '',
name: 'Result',
component: ResultsPage,
meta: {
index: 0,
title: 'Your Results',
svgPath: mdiAccountBoxMultiple,
parentIndex: -1
}
},
{
path: 'all',
name: 'Scores',
component: Scores,
meta: {
index: 1,
title: 'All Scores',
svgPath: mdiCounter,
parentIndex: -1
}
},
{
path: 'statistics',
name: 'Statistics',
component: Statistics,
meta: {
index: 2,
title: 'Statistics',
svgPath: mdiChartBox,
parentIndex: -1
}
},
{
path: 'answers',
name: 'Answers',
component: Answers,
meta: {
index: 3,
title: 'Global Answers',
svgPath: mdiEarth,
parentIndex: -1
}
}
]
},
{
path: '/404',
name: 'Error404',
component: Error404,
meta: {
index: -1,
title: 'Error 404: Not Found',
svgPath: mdiWeb
}
},
{
path: "/:catchAll(.*)",
name: 'NotFound',
redirect: '/404',
meta: {
index: -1,
title: 'Error 404: Not Found',
svgPath: mdiWeb
}
},
{
path: "/err_refused",
name: 'Refused',
component: Refused,
meta: {
index: -1,
title: 'Error: Connection Refused',
svgPath: mdiWeb
}
}
]
@ -100,8 +220,8 @@ Router.beforeEach((to, from, next) => {
})
Router.afterEach( (to, from) => {
const enterActiveClass = 'transition-all duration-300 ease-in'
const leaveActiveClass = 'transition-all duration-300 ease-out'
const enterActiveClass = 'transition-all duration-300 origin-top ease-in'
const leaveActiveClass = 'transition-all duration-300 origin-top ease-out'
const baseFromClass = 'transform opacity-0 md:scale-75'
if ( to.fullPath.split('/')[1] !== from.fullPath.split('/')[1] || !from.matched.length ) {
const fromIndex = from.meta.parentIndex ? from.meta.parentIndex : from.meta.index

View File

@ -1,19 +1,24 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
import AccountQuestion from '@/components/icons/AccountQuestion.vue'
export default {
name: 'Home',
components: {
AccountQuestion,
Content
}
Content,
Header,
TextFrame
}
}
</script>
<template>
<Content>
<template v-slot:header>
<TextFrame>
<Header>
home
</template>
<template v-slot:content>
</Header>
<Content>
<article class="prose mx-auto">
<p class="text-leader">
The road ahead is long and meandering.
@ -28,11 +33,11 @@
</p>
<div class="w-fit mx-auto">
<router-link active-class="active-link" class="navlink" to="/quiz">
<div class="inline-flex px-3 py-1 space-x-2 rounded-md bg-lime-800 text-white hover:bg-lime-600">
<div class="inline-flex px-3 py-1 space-x-2 rounded-md bg-lime-800 text-white transition-all duration-300 ease-in-out hover:bg-lime-600">
<span class="scale-75">
<AccountQuestion/>
</span>
<span>Take the Quiz</span>
<span class="uncial-antiqua">Take the Quiz</span>
</div>
</router-link>
</div>
@ -42,6 +47,6 @@
I would nevertheless implore you to play <em>Wanderhome</em> if you haven&rsquo;t already.
</p>
</article>
</template>
</Content>
</TextFrame>
</template>

View File

@ -1,18 +1,22 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'About',
components: {
Content
Content,
Header,
TextFrame
}
}
</script>
<template>
<div >
<Content>
<template v-slot:header>
<TextFrame>
<Header>
Background
</template>
<template v-slot:content>
</Header>
<Content>
<article class="prose mx-auto">
<h2>What Exactly Is This?</h2>
<p>
@ -87,9 +91,8 @@
It will apply all the things I currently know, and also add websockets and state synchronisation to the mix.
</p>
</article>
</template>
</Content>
</div>
</TextFrame>
</template>
<style scoped>
</style>

View File

@ -1,18 +1,23 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'Acknowledgements',
components: {
Content
Content,
Header,
TextFrame
}
}
</script>
<template>
<div>
<Content>
<template v-slot:header>
<TextFrame>
<Header>
Acknowledgements
</template>
<template v-slot:content>
</Header>
<Content>
<article class="prose mx-auto">
<p>
First and foremost, I am delighted that Jay Dragon and Possum Creek Games created such a magnificent work of art.
@ -27,7 +32,7 @@
This section is still a work in progress because the programme is still not yet finished.
</p>
</article>
</template>
</Content>
</TextFrame>
</div>
</template>

View File

@ -1,9 +1,8 @@
<script>
import Content from '@/components/Content.vue'
import Sidebar from '@/components/sidebar/Index.vue'
export default {
name: 'AboutIndex',
components: {
Content,
Sidebar
}
}

View File

@ -1,18 +1,23 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'Wanderhome',
components: {
Content
Content,
Header,
TextFrame
}
}
</script>
<template>
<div>
<Content>
<template v-slot:header>
<TextFrame>
<Header>
About <em>Wanderhome</em>
</template>
<template v-slot:content>
</Header>
<Content>
<article class="prose mx-auto">
<p>
<em class="uncial-antiqua">Wanderhome</em> is a pastoral fantasy table-top role-playing game by Jay Dragon, published by Possum Creek Games.
@ -39,7 +44,7 @@
If you are one of my friends and would like to play <em class="uncial-antiqua">Wanderhome</em>, let me know!
</p>
</article>
</template>
</Content>
</TextFrame>
</div>
</template>

View File

@ -0,0 +1,38 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'Error404',
components: {
Content,
Header,
TextFrame
}
}
</script>
<template>
<div >
<TextFrame>
<Header>
404: Not Found
</Header>
<Content>
<article class="prose mx-auto">
<p class="text-leader">
Uh-oh.
</p>
<p>
Sometimes we can get lost along the path.
</p>
<p>
But it&rsquo;s okay.
Just retrace your steps and you will find your way back again.
</p>
</article>
</Content>
</TextFrame>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,34 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'Refused',
components: {
Content,
Header,
TextFrame
}
}
</script>
<template>
<div >
<TextFrame>
<Header>
Error: Connection Refused
</Header>
<Content>
<article class="prose mx-auto">
<p class="text-leader">
The client is unable to connect to the server.
That&rsquo;s okay.
Sometimes, we run into obstacles along the way.
And we just need to try again after a while.
</p>
</article>
</Content>
</TextFrame>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,63 @@
<script>
import axios from 'axios'
import Config from '@/config.js'
import { useQuestionStore } from '@/stores/questions.js'
import { useAnswersStore } from '@/stores/answers.js'
import { useAppStore } from '@/stores/app.js'
export default {
setup() {
const questionStore = useQuestionStore()
const answersStore = useAnswersStore()
const appStore = useAppStore()
return {
questionStore,
answersStore,
appStore
}
},
name: 'QuestionIndex',
created() {
if (!this.appStore.hasData) {
this.getQuestions()
}
},
data() {
return {
error: null,
loading: false,
questions: null
}
},
methods: {
getQuestions() {
this.error = this.questions = null
this.loading = true
axios.get(`${Config.SERVER}api/questions/`)
.then((response) => {
this.error = false
this.questionStore.storeQuestions(response.data)
this.appStore.toggleData()
})
.catch( error => {
this.$router.push('/err_refused')
})
}
}
}
</script>
<template>
<div>
<router-view v-slot="{ Component, route }" class="w-full" ref="panel" appear>
<Transition mode="out-in"
enter-active-class="transition-all duration-300 origin-center ease-in"
enter-from-class="transform opacity-0 md:scale-95"
leave-active-class="transition-all duration-300 origin-center ease-out"
leave-to-class="transform opacity-0 md:scale-95"
>
<component :is="Component" :key="route.path"/>
</Transition>
</router-view>
</div>
</template>

View File

@ -0,0 +1,219 @@
<script>
import axios from 'axios'
import Config from '@/config.js'
import { useQuestionStore } from '@/stores/questions.js'
import { useAnswersStore } from '@/stores/answers.js'
import { useAppStore } from '@/stores/app.js'
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
import ArrowLeftBold from '@/components/icons/ArrowLeftBold.vue'
import ArrowRightBold from '@/components/icons/ArrowRightBold.vue'
import FleurDeLis from '@/components/icons/FleurDeLis.vue'
export default {
setup() {
const questionStore = useQuestionStore()
const answersStore = useAnswersStore()
const appStore = useAppStore()
return {
questionStore,
answersStore,
appStore
}
},
components: {
ArrowLeftBold,
ArrowRightBold,
Content,
FleurDeLis,
Header,
TextFrame
},
created() {
this.answersStore.makeArray(this.id, this.questionStore.questions[this.id].select)
},
mounted() {
this.$watch(
() => this.$route.params, (toParams, previousParams) => {
if (this.$route.name == 'Question') {
if (toParams.id >= this.questionStore.questions.length || this.id < 0) {
this.$router.push('/404')
} else {
if ( toParams.id == 0 ) {
this.enableBack = false
}
if ( toParams.id == this.questionStore.questions.length - 1 ) {
console.log('Condition met')
this.enableNext = false
} else {
this.enableNext = true
}
}
}
}
)
this.$watch(
() => this.appStore.hasData, (newValue) => {
if (this.id >= this.questionStore.questions.length || this.id < 0) {
this.$router.push('/404')
} else {
this.answersStore.makeArray(this.id, this.questionStore.questions[this.id].select)
if ( this.id == this.questionStore.questions.length -1 ) {
this.enableNext = false
} else {
this.enableNext = true
}
if ( this.id == 0 ) {
this.enableBack = false
} else {
this.enableBack = true
}
}
}
)
},
data() {
return {
question: null,
select: null,
answers: null
}
},
methods: {
submitAnswers() {
const data = JSON.stringify(this.answersStore.answers)
axios.post(
`${Config.SERVER}api/submit/`,
data,
{
headers: {
'Content-Type': 'application/json'
}
}
).then( (response) => {
this.appStore.storeResults(response.data)
console.log('Results fetched from the server.')
this.$router.push('/results')
}).catch( (error) => {
this.$router.push('/err_refused')
})
}
},
props: {
id: {
type: Number,
required: true
}
},
}
</script>
<template>
<TextFrame>
<Header>
Take the Quiz
</Header>
<Content>
<div v-if="questionStore.questions[this.id]">
<article class="prose">
<div v-html="questionStore.questions[this.id].question"></div>
</article>
<div class="grid grid-cols-1 content-evenly items-center gap-2 justify-center my-12 mx-auto md:grid-cols-2 lg:grid-cols-3" :aria-multiselectable="questionStore.questions[this.id].select > 1 ? true: false">
<div v-for="(answer, index) in questionStore.questions[this.id].answers" :key="`q${this.id}-o${index}`" class="inline-flex">
<input :type="this.questionStore.questions[this.id].select > 1 ? 'checkbox' : 'radio'" class="checkbox" :id="`q${this.id}-o${index}`" :value=index :key="`q${this.id}-o${index}`" v-model="answersStore.answers[this.id]" :disabled="this.questionStore.questions[this.id].select > 1 && this.answersStore.answers[this.id].length == this.questionStore.questions[this.id].select && !this.answersStore.answers[this.id].includes(index)">
<label :for="`q${this.id}-o${index}`">
<div v-html="answer" class="uncial-antiqua w-full h-full p-2 transition-all duration-100 ease-in-out hover:bg-lime-200 hover:text-orange-600 hover:cursor-pointer" :aria-label="answer" :title="answer"></div>
</label>
</div>
</div>
<div class="w-full flex mx-auto items-center justify-between max-w-sm">
<router-link :to="`/quiz/question/${this.id-1}`" v-if="this.id > 0">
<div class="inline-flex h-full px-3 py-1 space-x-1 rounded-md bg-lime-800 text-white items-center transition-all duration-300 ease-in-out hover:bg-lime-600" title="Back">
<span class="scale-100">
<ArrowLeftBold/>
</span>
<span class="uncial-antiqua">Back</span>
</div>
</router-link>
<div class="inline-flex h-full px-3 py-1 space-x-1 rounded-md bg-lime-800 opacity-50 text-white items-center hover:cursor-not-allowed" v-else>
<span class="scale-100">
<ArrowLeftBold/>
</span>
<span class="uncial-antiqua">Back</span>
</div>
<router-link :to="`/quiz/question/${this.id+1}`" v-if="this.id < this.questionStore.questions.length - 1 && this.answersStore.isAnswered(this.id)">
<div class="inline-flex h-full px-3 py-1 space-x-1 rounded-md bg-lime-800 text-white items-center transition-all duration-300 ease-in-out hover:bg-lime-600" title="Next">
<span class="uncial-antiqua">Next</span>
<span class="scale-100">
<ArrowRightBold/>
</span>
</div>
</router-link>
<div class="inline-flex h-full px-3 py-1 space-x-1 rounded-md bg-lime-800 opacity-50 text-white items-center hover:cursor-not-allowed" v-else>
<span class="uncial-antiqua">Next</span>
<span class="scale-100">
<ArrowRightBold/>
</span>
</div>
</div>
<Transition
enter-active-class="transition-all duration-300 origin-center ease-in"
enter-from-class="transform opacity-0 md:scale-95"
leave-active-class="transition-all duration-300 origin-center ease-out"
leave-to-class="transform opacity-0 md:scale-95"
>
<div v-show="this.id == this.questionStore.questions.length -1 && this.answersStore.isAnswered(this.id)" class="mx-auto my-8 w-fit transition-all">
<button @click="this.submitAnswers()">
<div class="inline-flex px-3 py-1 space-x-2 rounded-md bg-lime-800 text-white transition-all duration-300 ease-in-out hover:bg-lime-600">
<span class="scale-75">
<FleurDeLis/>
</span>
<span class="uncial-antiqua">Submit Answers</span>
<span class="scale-75">
<FleurDeLis/>
</span>
</div>
</button>
</div>
</Transition>
</div>
<div v-else>
Loading
</div>
</Content>
</TextFrame>
</template>
<style scoped>
.checkbox {
position: absolute;
opacity: 0;
}
:focus + label>div {
@apply bg-lime-200;
@apply text-orange-600;
}
:checked + label>div {
@apply border-lime-600;
@apply border-solid;
@apply border-l-2;
@apply text-orange-600;
}
:not(:checked):disabled + label>div {
@apply opacity-50;
@apply cursor-not-allowed;
@apply hover:bg-inherit;
@apply hover:text-inherit;
}
:checked:disabled + label>div {
@apply border-2;
@apply border-lime-600;
@apply border-solid;
}
</style>

View File

@ -0,0 +1,61 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
import RoadVariant from '@/components/icons/RoadVariant.vue'
export default {
name: 'QuizConsole',
components: {
Content,
Header,
RoadVariant,
TextFrame
}
}
</script>
<template>
<div>
<TextFrame>
<Header>
Take the Quiz
</Header>
<Content>
<article class="prose mx-auto">
<h2 class="uncial-antiqua" key="welcome-0">
Gather your Things
</h2>
<p class="text-leader" key="welcome-1">
We are about to embark upon a journey.
</p>
<p class="text-leader" key="welcome-2">
In a minute, I will ask you a series of multiple choice questions.
For most of them, you will need to select the one answer.
Some questions will need multiple answers.
Don&rsquo;t over-think them!
</p>
<p class="text-leader" key="welcome-3">
<em class="uncial-antiqua">Wanderhome</em> is about going wherever the road takes you, and reflecting on how the journey changes us.
The road is full of myriad travellers, each with their rich lives and stories.
This quiz will help you figure out what kinds of stories you will bring to the table.
</p>
<p class="text-leader" key="welcome-4">
Will you join me?
</p>
<div class="w-fit mx-auto" key="welcome-5">
<router-link active-class="active-link" class="navlink" to="/quiz/question/0">
<div class="inline-flex px-3 py-1 space-x-2 rounded-md bg-lime-800 text-white transition-all duration-300 ease-in-out hover:bg-lime-600">
<span class="scale-75">
<RoadVariant/>
</span>
<span class="uncial-antiqua">Let&rsquo;s Go</span>
</div>
</router-link>
</div>
</article>
</Content>
</TextFrame>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,23 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'Answers',
components: {
Content,
Header,
TextFrame
}
}
</script>
<template>
<TextFrame>
<Header>
Global Answers
</Header>
<Content>
</Content>
</TextFrame>
</template>

View File

@ -0,0 +1,34 @@
<script>
import Sidebar from '@/components/sidebar/Index.vue'
export default {
name: 'ResultsIndex',
components: {
Sidebar
}
}
</script>
<template>
<div>
<div class="flex flex-col space-y-3 md:flex-row md:space-x-6 md:space-y-0">
<div class="flex flex-col w-full md:w-1/3 h-fit">
<Sidebar :prefix="$router.options.routes[3].path" :routes="$router.options.routes[3].children">
<template v-slot:header>
Results
</template>
</Sidebar>
</div>
<div class="flex w-full md:w-2/3">
<router-view v-slot="{ Component, route }" class="w-full" ref="panel" appear>
<Transition mode="out-in"
:enter-active-class="route.meta.panelEnterActiveClass"
:enter-from-class="route.meta.panelEnterFromClass"
:leave-active-class="route.meta.panelLeaveActiveClass"
:leave-to-class="route.meta.panelLeaveToClass"
>
<component :is="Component" />
</Transition>
</router-view>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,78 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
import { useAppStore } from '@/stores/app.js'
export default {
setup() {
const appStore = useAppStore()
return {
appStore
}
},
name: 'Results',
components: {
Content,
Header,
TextFrame
},
methods: {
renderAnimals(rawText) {
var output = ''
var length = rawText.length
for (var index = 0; index < length; index++) {
var article = ['a','e','i','o','u'].includes(rawText[index].slice(0,1)) ? 'an' : 'a'
if (index == length - 2) {
var conjunction = ', or '
} else if (index == length -1) {
var conjunction = '.'
} else {
var conjunction = ', '
}
output += `${article} ${rawText[index]}${conjunction}`
}
return output
}
}
}
</script>
<template>
<TextFrame>
<Header>
<span v-if="this.appStore.results.playbooks.length > 1">Your Results</span>
<span v-else>Your Result</span>
</Header>
<Content>
<article class="prose">
<section v-for="playbook in this.appStore.results.playbooks" :key="Object.keys(playbook)[0]" class="border-solid border-b-2 border-lime-600 border-opacity-25">
<h2 class="flex justify-between">
<span class="uncial-antiqua align-bottom">
<span class="text-xl">The</span> <span class="text-3xl">{{ Object.keys(playbook)[0].slice(0,1).toUpperCase()+Object.keys(playbook)[0].slice(1) }}</span>
</span>
<span class="text-sm">Pages {{ Object.values(playbook)[0].pages }}</span>
</h2>
<p class="text-leader">
{{ Object.values(playbook)[0].flavour }}
</p>
<p>
{{ Object.values(playbook)[0].blurb }}
</p>
<p>
You are alive. Your care is <strong>{{ Object.values(playbook)[0].care }}</strong>.
</p>
<p>
Your animal form is {{ renderAnimals(Object.values(playbook)[0].animals) }}
</p>
<h4>
You can always do the following:
</h4>
<ul>
<li class="my-0 py-0 mx-3" v-for="(action, index) in Object.values(playbook)[0].actions" :key="Object.keys(playbook)[0] + index" v-html="action"></li>
</ul>
</section>
</article>
</Content>
</TextFrame>
</template>

View File

@ -0,0 +1,56 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
import { useAppStore } from '@/stores/app.js'
export default {
setup() {
const appStore = useAppStore()
return {
appStore
}
},
name: 'Scores',
components: {
Content,
Header,
TextFrame
}
}
</script>
<template>
<TextFrame>
<Header>
Scores
</Header>
<Content>
<p>
The following are your scores for each playbook:
</p>
<table class="table-auto w-full max-w-xs mx-auto">
<thead>
<tr>
<th>
Playbook
</th>
<th>
Score
</th>
</tr>
</thead>
<tbody>
<tr v-for="(score, playbook) in this.appStore.results.all_playbooks" :key="playbook">
<td>
The {{ playbook.slice(0,1).toUpperCase() + playbook.slice(1) }}
</td>
<td>
{{ Math.round(100*score/this.appStore.results.max_score) }} &#x25;
</td>
</tr>
</tbody>
</table>
</Content>
</TextFrame>
</template>

View File

@ -0,0 +1,23 @@
<script>
import Content from '@/components/Content.vue'
import Header from '@/components/Header.vue'
import TextFrame from '@/components/TextFrame.vue'
export default {
name: 'Statistics',
components: {
Content,
Header,
TextFrame
}
}
</script>
<template>
<TextFrame>
<Header>
Statistics
</Header>
<Content>
</Content>
</TextFrame>
</template>