This commit is contained in:
Markus Thielen 2023-03-17 10:51:05 +01:00
parent c7dd690ced
commit 44b8dc0c2f
Signed by: markus
GPG Key ID: 3D4980D3EC9C8E26
4 changed files with 226 additions and 144 deletions

View File

@ -13,55 +13,58 @@ import CommunityIcon from './icons/IconCommunity.vue'
</script>
<template>
<AboutItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>About</template>
<div class="row">
<div class="col-12 col-md-8 offset-md-2 col-lg-6 offset-lg-3">
<AboutItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>About</template>
<p>This is a very simple demo app for <a href="https://basebox.tech">basebox</a>,
the medical grade backend that helps you get your project done with ease.</p>
</AboutItem>
<p>This is a very simple demo app for <a href="https://basebox.tech">basebox</a>,
the medical grade backend that helps you get your project done with ease.</p>
</AboutItem>
<AboutItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tools and Languages</template>
<AboutItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tools and Languages</template>
<p>We built basebox in 100% <a href="https://rust-lang.org">Rust</a>, but you can use about
any tool, language or framework to use basebox as your backend. As long as it can send
<a href="https://graphql.org">GraphQL</a> requests and understands JSON, it can build on basebox!</p>
</AboutItem>
<p>We built basebox in 100% <a href="https://rust-lang.org">Rust</a>, but you can use about
any tool, language or framework to use basebox as your backend. As long as it can send
<a href="https://graphql.org">GraphQL</a> requests and understands JSON, it can build on basebox!</p>
</AboutItem>
<AboutItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Feedback</template>
<p><b>We are so eager to hear from you!</b></p>
<ul>
<li>Are you missing something?</li>
<li>Any suggestion?</li>
<li>Any question?</li>
</ul>
<p>We'd be REALLY happy to <a href="https://basebox.tech/open-a-aticket">hear from you</a>!</p>
</AboutItem>
<AboutItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Feedback</template>
<p><b>We are so eager to hear from you!</b></p>
<ul>
<li>Are you missing something?</li>
<li>Any suggestion?</li>
<li>Any question?</li>
</ul>
<p>We'd be REALLY happy to <a href="https://basebox.tech/open-a-aticket">hear from you</a>!</p>
</AboutItem>
<AboutItem>
<template #icon>
<QuestionIcon />
</template>
<template #heading>Support</template>
<AboutItem>
<template #icon>
<QuestionIcon />
</template>
<template #heading>Support</template>
If you need help, check out our <a href="https://basebox.tech">website</a>.<br>
Some support options:
<ul>
<li>See our <a href="https://basebox.tech/faq">FAQs</a></li>
<li>Read the <a href="https://docs.basebox.tech">documentation</a></li>
<li>Open a <a href="https://basebox.tech/open-a-ticket">ticket</a></li>
</ul>
</AboutItem>
If you need help, check out our <a href="https://basebox.tech">website</a>.<br>
Some support options:
<ul>
<li>See our <a href="https://basebox.tech/faq">FAQs</a></li>
<li>Read the <a href="https://docs.basebox.tech">documentation</a></li>
<li>Open a <a href="https://basebox.tech/open-a-ticket">ticket</a></li>
</ul>
</AboutItem>
</div>
</div>
</template>

View File

@ -12,6 +12,24 @@
ToDo List
</h1>
<!-- message modal -->
<div class="modal" tabindex="-1" id="msgbox">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="filter-form" class="toolbar">
<label class="switch">
@ -26,7 +44,7 @@
<label class="col-form-label" for="list-selector">Select List: </label>
<div class="ms-2">
<select id="list-selector" class="form-select" v-model="currentList">
<option :value="0">All</option>
<option :value="0" selected>All</option>
<option v-for="item in gStore.lists" :key="item.id" :value="item.id">{{ item.title }}</option>
</select>
</div>
@ -58,7 +76,12 @@
</div>
<div class="alert alert-info" v-if="filteredItems.length === 0">
<p><i class="bi bi-info-circle"></i>
Sorry, there are no todo items{{ currentList !== 0 ? " in this list" : ""}}.
</p>
<p v-if="currentList !== 0">You can add items or select another list.</p>
<hr>
<button type="button" class="btn btn-primary btn-sm" @click="addItem()">Create Todo Item</button>
</div>
</transition-group>
@ -69,7 +92,10 @@
<script>
import {gqlQuery} from "../util/net";
import {store} from "../store";
import {showError, store} from "../store";
import { Modal } from 'bootstrap';
const NEW_TASK_ID = "new-task-id";
export default {
name: "Todo",
@ -81,7 +107,7 @@ export default {
/** whether to show completed items */
showCompleted: true,
/* the id of the list currently being shown */
currentList: 1,
currentList: 0,
}
},
@ -92,23 +118,77 @@ export default {
* @param task the task to save; this is an object as received from the broker.
*/
saveTask(task) {
console.info(`Saving task '${task.title}' with id ${task.id}`);
let request = "";
if (task.id !== NEW_TASK_ID) {
/* save changes to existing task */
request = `mutation {
updateTask(
id: "${task.id}",
title: "${task.title}",
description: "${task.description}",
completed: ${task.completed ? "true" : "false"},
list: {
id: "${task.list.id}"
},
) {
id
}
}`;
} else {
/* create new task */
request = `mutation {
createTask(
title: "${task.title}",
description: "${task.description}",
completed: ${task.completed ? "true" : "false"},
list: {
id: "${task.list.id}"
},
user: {
username: "${store.session.username}"
}
) {
id
}
}`;
}
/* send query */
gqlQuery(
request
).then(data => {
task.id = data.id;
}).catch(e => {
const errMsg = `Failed to save task: ${e}`;
console.error(errMsg);
showError(errMsg);
});
},
/**
* Show a message nox (Bootstrap modal)
*/
showModal(title, text) {
const el = document.getElementById('msgbox');
el.querySelector(".modal-title").innerHTML = title;
el.querySelector(".modal-body").innerHTML = text;
const modal = new Modal(el);
modal.show();
},
/**
* Add an item to the current list.
*/
addItem() {
if (this.currentList === 0) {
window.alert("Please select a list at the top right before adding an item.");
if (!this.currentList) {
this.showModal("No list selected",
'Please select a specific list (not "All") before adding an item.');
return;
}
/* fake ID of new item */
const newItemId = store.tasks.length + 1;
store.tasks.push({
id: newItemId,
id: NEW_TASK_ID,
completed: false,
title: "Enter task here",
description: "",
@ -194,41 +274,6 @@ export default {
},
/**
* mounted lifecycle hook.
*/
mounted() {
if (!store.session) {
console.error("TodoRoot component must not be loaded if user is not logged in.");
return;
}
/* Load user info and his/her lists and todos */
gqlQuery(`query {
getUser(username: "${store.session.userName}") {
name
lists {
id
title
}
tasks {
id
title
description
completed
list {
id
}
}
}
}`).then(rspJson => {
}).catch(err => {
});
},
}
</script>

View File

@ -5,7 +5,7 @@
* https://basebox.tech
*/
import {showError, store} from "../store";
import {loggedIn, showError, store} from "../store";
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import { objectToQueryString } from "../util/net";
@ -41,7 +41,7 @@ router.beforeEach(async (to, from) => {
// Handle OAuth callback request.
// This request is coming in after the user entered her/his credentials at the IdP
// login form; the IdP (e.g. Keycloak) redirects the browser to the URL of this route.
if (to.path === "/oauth-callback") {
if (to.path.startsWith("/oauth-callback")) {
let queryString = objectToQueryString(to.query);
console.info(`Got /oauth-callback with query string '${queryString}`);
@ -58,6 +58,11 @@ router.beforeEach(async (to, from) => {
return {name: 'home'};
}
/* if no user is logged in, we redirect to the home page */
if (!loggedIn() && to.path.length > 1 && to.path !== "/about") {
return {name: 'home'};
}
});
export default router

View File

@ -22,32 +22,9 @@ export const store = reactive({
fatalError: "",
/* array of todo lists currently known */
lists: [
{ id: 1, title: "Default"},
{ id: 2, title: "House" },
{ id: 3, title: "Car" },
{ id: 4, title: "Work" },
],
lists: [],
/* known tasks */
tasks: [
{ id: 1, completed: false, title: "Go to dentist", description: "Last time is way too long ago...", list: { id: 1 } },
{ id: 2, completed: true, title: "Change engine oil", description: "Use the good one", list: { id: 3 } },
{ id: 3, completed: false, title: "Clean windows", description: "Regualar stuff", list: { id: 1 } },
{ id: 4, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 5, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 6, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 7, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 8, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 9, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 10, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 11, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 12, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 13, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 14, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 15, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 16, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 17, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
],
tasks: [],
});
@ -101,43 +78,95 @@ export async function storeInit(session) {
* We cannot run these requests in parallel, since the user record must exist in the database
* before we can create the default list.
*/
const tasks = [];
tasks.push(
gqlQuery(`mutation {
try {
console.info("NOTE: If next request fails, it is probably because the user already exists. In this case, the error is ignored.");
await gqlQuery(`mutation {
createUser(
username: "${session.username}",
name: "${session.first_name} ${session.last_name}"
) {
username
}
}`));
tasks.push(gqlQuery(`mutation {
createList(
title: "Default",
user: { username: "${session.username}" }
) {
title
}`);
} catch(e) {
const errStr = e.toString();
if (errStr.indexOf("already exists") === -1) {
/* this is an error */
const e = `Failed to create user: ${errStr}`;
console.error(e);
showError(e);
return;
}
}`));
}
const allErrors = [];
for (const task of tasks) {
try {
await task;
} catch (e) {
const errStr = e.toString();
if (errStr.indexOf("already exists") === -1) {
/* this is an error */
const e = `Init task failed: ${errStr}`;
console.error(e);
allErrors.push(e);
/* Load user info, lists and todos. */
gqlQuery(`query {
getUser(username: "${store.session.username}") {
name
lists {
id
title
}
tasks {
id
title
description
completed
list {
id
}
}
}
}
}`).then(data => {
store.lists = data.User.lists ? data.User.lists : [];
store.tasks = data.User.tasks ? data.User.tasks : [];
/* create default list if necessary */
if (store.lists.length === 0) {
gqlQuery(`mutation {
createList(
title: "Default",
user: { username: "${store.session.username}" }
) {
title
}
}`);
}
if (allErrors.length) {
showError(allErrors.join("<br><br>"));
}
/* fix for basebox bug: task.list is returned as an array of List objects, where we expect just a single object */
for (const task of store.tasks) {
if (Array.isArray(task.list)) {
task.list = task.list[0];
}
}
}
}).catch(err => {
const e = `Failed to load user/data: ${err.toString()}`;
console.error(e);
showError(e);
});
/* Check if we already have a list named "Default". */
}
const testData = {
lists: [
{ id: 1, title: "Default"},
{ id: 2, title: "House" },
{ id: 3, title: "Car" },
{ id: 4, title: "Work" },
],
/* known tasks */
tasks: [
{ id: 1, completed: false, title: "Go to dentist", description: "Last time is way too long ago...", list: { id: 1 } },
{ id: 2, completed: true, title: "Change engine oil", description: "Use the good one", list: { id: 3 } },
{ id: 3, completed: false, title: "Clean windows", description: "Regualar stuff", list: { id: 1 } },
{ id: 4, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ id: 5, completed: false, title: "Do more benchmarking", description: "Find out how fast this thing really is", list: { id: 4 } },
],
};