WIP
This commit is contained in:
parent
c7dd690ced
commit
44b8dc0c2f
@ -13,55 +13,58 @@ import CommunityIcon from './icons/IconCommunity.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AboutItem>
|
<div class="row">
|
||||||
<template #icon>
|
<div class="col-12 col-md-8 offset-md-2 col-lg-6 offset-lg-3">
|
||||||
<DocumentationIcon />
|
<AboutItem>
|
||||||
</template>
|
<template #icon>
|
||||||
<template #heading>About</template>
|
<DocumentationIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>About</template>
|
||||||
|
|
||||||
<p>This is a very simple demo app for <a href="https://basebox.tech">basebox</a>,
|
<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>
|
the medical grade backend that helps you get your project done with ease.</p>
|
||||||
</AboutItem>
|
</AboutItem>
|
||||||
|
|
||||||
<AboutItem>
|
<AboutItem>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<ToolingIcon />
|
<ToolingIcon />
|
||||||
</template>
|
</template>
|
||||||
<template #heading>Tools and Languages</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
|
<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
|
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>
|
<a href="https://graphql.org">GraphQL</a> requests and understands JSON, it can build on basebox!</p>
|
||||||
</AboutItem>
|
</AboutItem>
|
||||||
|
|
||||||
<AboutItem>
|
<AboutItem>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<CommunityIcon />
|
<CommunityIcon />
|
||||||
</template>
|
</template>
|
||||||
<template #heading>Feedback</template>
|
<template #heading>Feedback</template>
|
||||||
<p><b>We are so eager to hear from you!</b></p>
|
<p><b>We are so eager to hear from you!</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Are you missing something?</li>
|
<li>Are you missing something?</li>
|
||||||
<li>Any suggestion?</li>
|
<li>Any suggestion?</li>
|
||||||
<li>Any question?</li>
|
<li>Any question?</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>We'd be REALLY happy to <a href="https://basebox.tech/open-a-aticket">hear from you</a>!</p>
|
<p>We'd be REALLY happy to <a href="https://basebox.tech/open-a-aticket">hear from you</a>!</p>
|
||||||
</AboutItem>
|
</AboutItem>
|
||||||
|
|
||||||
<AboutItem>
|
<AboutItem>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<QuestionIcon />
|
<QuestionIcon />
|
||||||
</template>
|
</template>
|
||||||
<template #heading>Support</template>
|
<template #heading>Support</template>
|
||||||
|
|
||||||
If you need help, check out our <a href="https://basebox.tech">website</a>.<br>
|
If you need help, check out our <a href="https://basebox.tech">website</a>.<br>
|
||||||
Some support options:
|
Some support options:
|
||||||
<ul>
|
<ul>
|
||||||
<li>See our <a href="https://basebox.tech/faq">FAQs</a></li>
|
<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>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>
|
<li>Open a <a href="https://basebox.tech/open-a-ticket">ticket</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</AboutItem>
|
|
||||||
|
|
||||||
|
</AboutItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -12,6 +12,24 @@
|
|||||||
ToDo List
|
ToDo List
|
||||||
</h1>
|
</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">
|
<div id="filter-form" class="toolbar">
|
||||||
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
@ -26,7 +44,7 @@
|
|||||||
<label class="col-form-label" for="list-selector">Select List: </label>
|
<label class="col-form-label" for="list-selector">Select List: </label>
|
||||||
<div class="ms-2">
|
<div class="ms-2">
|
||||||
<select id="list-selector" class="form-select" v-model="currentList">
|
<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>
|
<option v-for="item in gStore.lists" :key="item.id" :value="item.id">{{ item.title }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +76,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-info" v-if="filteredItems.length === 0">
|
<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>
|
</div>
|
||||||
|
|
||||||
</transition-group>
|
</transition-group>
|
||||||
@ -69,7 +92,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {gqlQuery} from "../util/net";
|
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 {
|
export default {
|
||||||
name: "Todo",
|
name: "Todo",
|
||||||
@ -81,7 +107,7 @@ export default {
|
|||||||
/** whether to show completed items */
|
/** whether to show completed items */
|
||||||
showCompleted: true,
|
showCompleted: true,
|
||||||
/* the id of the list currently being shown */
|
/* 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.
|
* @param task the task to save; this is an object as received from the broker.
|
||||||
*/
|
*/
|
||||||
saveTask(task) {
|
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.
|
* Add an item to the current list.
|
||||||
*/
|
*/
|
||||||
addItem() {
|
addItem() {
|
||||||
if (this.currentList === 0) {
|
if (!this.currentList) {
|
||||||
window.alert("Please select a list at the top right before adding an item.");
|
this.showModal("No list selected",
|
||||||
|
'Please select a specific list (not "All") before adding an item.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fake ID of new item */
|
|
||||||
const newItemId = store.tasks.length + 1;
|
|
||||||
|
|
||||||
store.tasks.push({
|
store.tasks.push({
|
||||||
id: newItemId,
|
id: NEW_TASK_ID,
|
||||||
completed: false,
|
completed: false,
|
||||||
title: "Enter task here",
|
title: "Enter task here",
|
||||||
description: "",
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* https://basebox.tech
|
* https://basebox.tech
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {showError, store} from "../store";
|
import {loggedIn, showError, store} from "../store";
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import { objectToQueryString } from "../util/net";
|
import { objectToQueryString } from "../util/net";
|
||||||
@ -41,7 +41,7 @@ router.beforeEach(async (to, from) => {
|
|||||||
// Handle OAuth callback request.
|
// Handle OAuth callback request.
|
||||||
// This request is coming in after the user entered her/his credentials at the IdP
|
// 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.
|
// 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);
|
let queryString = objectToQueryString(to.query);
|
||||||
console.info(`Got /oauth-callback with query string '${queryString}`);
|
console.info(`Got /oauth-callback with query string '${queryString}`);
|
||||||
|
|
||||||
@ -58,6 +58,11 @@ router.beforeEach(async (to, from) => {
|
|||||||
return {name: 'home'};
|
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
|
export default router
|
||||||
|
135
src/store.js
135
src/store.js
@ -22,32 +22,9 @@ export const store = reactive({
|
|||||||
fatalError: "",
|
fatalError: "",
|
||||||
|
|
||||||
/* array of todo lists currently known */
|
/* array of todo lists currently known */
|
||||||
lists: [
|
lists: [],
|
||||||
{ id: 1, title: "Default"},
|
|
||||||
{ id: 2, title: "House" },
|
|
||||||
{ id: 3, title: "Car" },
|
|
||||||
{ id: 4, title: "Work" },
|
|
||||||
],
|
|
||||||
/* known tasks */
|
/* known tasks */
|
||||||
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 } },
|
|
||||||
],
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -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
|
* We cannot run these requests in parallel, since the user record must exist in the database
|
||||||
* before we can create the default list.
|
* before we can create the default list.
|
||||||
*/
|
*/
|
||||||
const tasks = [];
|
try {
|
||||||
tasks.push(
|
console.info("NOTE: If next request fails, it is probably because the user already exists. In this case, the error is ignored.");
|
||||||
gqlQuery(`mutation {
|
await gqlQuery(`mutation {
|
||||||
createUser(
|
createUser(
|
||||||
username: "${session.username}",
|
username: "${session.username}",
|
||||||
name: "${session.first_name} ${session.last_name}"
|
name: "${session.first_name} ${session.last_name}"
|
||||||
) {
|
) {
|
||||||
username
|
username
|
||||||
}
|
}
|
||||||
}`));
|
}`);
|
||||||
tasks.push(gqlQuery(`mutation {
|
} catch(e) {
|
||||||
createList(
|
const errStr = e.toString();
|
||||||
title: "Default",
|
if (errStr.indexOf("already exists") === -1) {
|
||||||
user: { username: "${session.username}" }
|
/* this is an error */
|
||||||
) {
|
const e = `Failed to create user: ${errStr}`;
|
||||||
title
|
console.error(e);
|
||||||
|
showError(e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}`));
|
}
|
||||||
|
|
||||||
const allErrors = [];
|
/* Load user info, lists and todos. */
|
||||||
|
gqlQuery(`query {
|
||||||
for (const task of tasks) {
|
getUser(username: "${store.session.username}") {
|
||||||
try {
|
name
|
||||||
await task;
|
lists {
|
||||||
} catch (e) {
|
id
|
||||||
const errStr = e.toString();
|
title
|
||||||
if (errStr.indexOf("already exists") === -1) {
|
}
|
||||||
/* this is an error */
|
tasks {
|
||||||
const e = `Init task failed: ${errStr}`;
|
id
|
||||||
console.error(e);
|
title
|
||||||
allErrors.push(e);
|
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) {
|
/* fix for basebox bug: task.list is returned as an array of List objects, where we expect just a single object */
|
||||||
showError(allErrors.join("<br><br>"));
|
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 } },
|
||||||
|
],
|
||||||
|
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user