Converted sample project into an almost working Svelte / Meteor project. Needs to fix the login/logout code, the camera code, and add Chromebook history when the QR Code is scanned.
This commit is contained in:
52
imports/api/data-collection.js
Normal file
52
imports/api/data-collection.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
//export const Records = new Mongo.Collection('records');
|
||||
let client;
|
||||
let database;
|
||||
let dataCollection;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
let uri = process.env.MONGO_URL2;
|
||||
|
||||
client = new MongoClient(uri);
|
||||
database = client.db("avusd-data-collection");
|
||||
dataCollection = database.collection("records");
|
||||
|
||||
// This code only runs on the server
|
||||
Meteor.publish('chromebookData', function(deviceId) {
|
||||
check(deviceId, String);
|
||||
|
||||
return dataCollection.find({deviceId});
|
||||
});
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
// 'tasks.setChecked'(taskId, setChecked) {
|
||||
// check(taskId, String);
|
||||
// check(setChecked, Boolean);
|
||||
//
|
||||
// const task = Tasks.findOne(taskId);
|
||||
// if (task.private && task.owner !== this.userId) {
|
||||
// // If the task is private, make sure only the owner can check it off
|
||||
// throw new Meteor.Error('not-authorized');
|
||||
// }
|
||||
//
|
||||
// Tasks.update(taskId, { $set: { checked: setChecked } });
|
||||
// },
|
||||
// 'tasks.setPrivate'(taskId, setToPrivate) {
|
||||
// check(taskId, String);
|
||||
// check(setToPrivate, Boolean);
|
||||
//
|
||||
// const task = Tasks.findOne(taskId);
|
||||
//
|
||||
// // Make sure only the task owner can make a task private
|
||||
// if (task.owner !== this.userId) {
|
||||
// throw new Meteor.Error('not-authorized');
|
||||
// }
|
||||
//
|
||||
// Tasks.update(taskId, { $set: { private: setToPrivate } });
|
||||
// },
|
||||
});
|
||||
@@ -2,9 +2,21 @@ import { Meteor } from 'meteor/meteor';
|
||||
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.methods({
|
||||
'users.login'() {
|
||||
console.log("Login Called.");
|
||||
}
|
||||
});
|
||||
}
|
||||
Meteor.publish(null, function() {
|
||||
if(this.userId) {
|
||||
return Meteor.roleAssignment.find({'user._id': this.userId});
|
||||
}
|
||||
else {
|
||||
this.ready();
|
||||
}
|
||||
})
|
||||
|
||||
// Meteor.methods({
|
||||
// 'users.setupInitialRoles'() {
|
||||
// Roles.createRole('admin');
|
||||
// Roles.createRole('laptop-management');
|
||||
// Roles.addRolesToParent('laptop-management', 'admin');
|
||||
// Roles.addUsersToRoles("zwbMiaSKHix4bWQ8d", 'admin', 'global');
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Accounts } from 'meteor/accounts-base'
|
||||
|
||||
Accounts.ui.config({
|
||||
passwordSignupFields: 'USERNAME_ONLY'
|
||||
});
|
||||
if(Meteor.isCLient) {
|
||||
Accounts.ui.config({
|
||||
passwordSignupFields: 'USERNAME_ONLY'
|
||||
});
|
||||
}
|
||||
|
||||
Accounts.config({
|
||||
restrictCreationByEmailDomain: function(address) {
|
||||
|
||||
5
imports/ui/App.css
Normal file
5
imports/ui/App.css
Normal file
@@ -0,0 +1,5 @@
|
||||
nav {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=App.css.map */
|
||||
1
imports/ui/App.css.map
Normal file
1
imports/ui/App.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["App.sass"],"names":[],"mappings":"AAAA;EACC","file":"App.css"}
|
||||
2
imports/ui/App.sass
Normal file
2
imports/ui/App.sass
Normal file
@@ -0,0 +1,2 @@
|
||||
nav
|
||||
font-size: 1.4rem
|
||||
@@ -1,12 +1,159 @@
|
||||
|
||||
<style>
|
||||
nav {
|
||||
font-size: 2rem;
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
color: green;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #000121;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
||||
}
|
||||
|
||||
/** Forbidden CSS */
|
||||
.maincontainer {
|
||||
position: relative;
|
||||
top: -50px;
|
||||
transform: scale(0.8);
|
||||
background: url("/public/images/forbidden/HauntedHouseBackground.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 700px 600px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
margin: 0px auto;
|
||||
display: grid;
|
||||
}
|
||||
.foregroundimg {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
top: -230px;
|
||||
z-index: 5;
|
||||
}
|
||||
.errorcode {
|
||||
position: relative;
|
||||
top: -200px;
|
||||
font-family: 'Creepster', cursive;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 6em;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
.errortext {
|
||||
position: relative;
|
||||
top: -260px;
|
||||
color: #FBD130;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.bat {
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
transform-origin: center;
|
||||
z-index: 3;
|
||||
}
|
||||
.bat:nth-child(1) {
|
||||
top: 380px;
|
||||
left: 120px;
|
||||
transform: scale(0.5);
|
||||
animation: 13s 1s flyBat1 infinite linear;
|
||||
}
|
||||
.bat:nth-child(2) {
|
||||
top: 280px;
|
||||
left: 80px;
|
||||
transform: scale(0.3);
|
||||
animation: 8s 4s flyBat2 infinite linear;
|
||||
}
|
||||
.bat:nth-child(3) {
|
||||
top: 200px;
|
||||
left: 150px;
|
||||
transform: scale(0.4);
|
||||
animation: 12s 2s flyBat3 infinite linear;
|
||||
}
|
||||
.body {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
top: 12px;
|
||||
}
|
||||
.wing {
|
||||
width: 150px;
|
||||
position: relative;
|
||||
transform-origin: right center;
|
||||
}
|
||||
.leftwing {
|
||||
left: 30px;
|
||||
animation: 0.8s flapLeft infinite ease-in-out;
|
||||
}
|
||||
.rightwing {
|
||||
left: -180px;
|
||||
transform: scaleX(-1);
|
||||
animation: 0.8s flapRight infinite ease-in-out;
|
||||
}
|
||||
@keyframes flapLeft {
|
||||
0% { transform: rotateZ(0); }
|
||||
50% { transform: rotateZ(10deg) rotateY(40deg); }
|
||||
100% { transform: rotateZ(0); }
|
||||
}
|
||||
@keyframes flapRight {
|
||||
0% { transform: scaleX(-1) rotateZ(0); }
|
||||
50% { transform: scaleX(-1) rotateZ(10deg) rotateY(40deg); }
|
||||
100% { transform: scaleX(-1) rotateZ(0); }
|
||||
}
|
||||
@keyframes flyBat1 {
|
||||
0% { opacity: 1; transform: scale(0.5)}
|
||||
25% { opacity: 1; transform: scale(0.5) translate(-400px, -330px) }
|
||||
50% { opacity: 1; transform: scale(0.5) translate(400px, -800px) }
|
||||
75% { opacity: 1; transform: scale(0.5) translate(600px, 100px) }
|
||||
100% { opacity: 1; transform: scale(0.5) translate(100px, 300px) }
|
||||
}
|
||||
@keyframes flyBat2 {
|
||||
0% { opacity: 1; transform: scale(0.3)}
|
||||
25% { opacity: 1; transform: scale(0.3) translate(200px, -330px) }
|
||||
50% { opacity: 1; transform: scale(0.3) translate(-300px, -800px) }
|
||||
75% { opacity: 1; transform: scale(0.3) translate(-400px, 100px) }
|
||||
100% { opacity: 1; transform: scale(0.3) translate(100px, 300px) }
|
||||
}
|
||||
@keyframes flyBat3 {
|
||||
0% { opacity: 1; transform: scale(0.4)}
|
||||
25% { opacity: 1; transform: scale(0.4) translate(-350px, -330px) }
|
||||
50% { opacity: 1; transform: scale(0.4) translate(400px, -800px) }
|
||||
75% { opacity: 1; transform: scale(0.4) translate(-600px, 100px) }
|
||||
100% { opacity: 1; transform: scale(0.4) translate(100px, 300px) }
|
||||
}
|
||||
/*@media only screen and (max-width: 850px) {
|
||||
.maincontainer {
|
||||
transform: scale(0.6);
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
background-size: 600px 400px;
|
||||
}
|
||||
|
||||
.errortext {
|
||||
font-size: 1em;
|
||||
}
|
||||
}*/
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {Route} from 'tinro';
|
||||
import {onMount} from 'svelte';
|
||||
import {useTracker} from 'meteor/rdb:svelte-meteor-data';
|
||||
import {Roles} from 'meteor/alanning:roles';
|
||||
import Chromebooks from './Chromebooks.svelte';
|
||||
import {BlazeTemplate} from 'meteor/svelte:blaze-integration';
|
||||
import {Records} from '../api/records.js'
|
||||
import ServiceConfiguration from "meteor/service-configuration";
|
||||
|
||||
let currentUser;
|
||||
//import './imports/ui/App.css';
|
||||
|
||||
//let currentUser;
|
||||
|
||||
onMount(async () => {
|
||||
// Meteor.subscribe('records');
|
||||
@@ -15,6 +162,18 @@
|
||||
// $: incompleteCount = useTracker(() => Tasks.find({checked: {$ne: true}}).count());
|
||||
|
||||
$: currentUser = useTracker(() => Meteor.user());
|
||||
$: isAdmin = useTracker(() => Roles.userIsInRole(currentUser._id, 'laptop-management', 'global'));
|
||||
|
||||
// Tracker.autorun(() => {
|
||||
// let user = Meteor.user();
|
||||
// let isManagement = user ? Roles.userIsInRole(user._id, 'laptop-management', 'global') : 0;
|
||||
//
|
||||
// if(user && isManagement) {
|
||||
// new ProcessLaptops({
|
||||
// target: document.getElementById('appView')
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// const taskStore = Tasks.find({}, {sort: {createdAt: -1}});
|
||||
// $: {
|
||||
@@ -45,43 +204,66 @@
|
||||
function performLogout() {
|
||||
Meteor.logout();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<!-- <h1>Todo List ({ $incompleteCount })</h1>-->
|
||||
<!-- <label className="hide-completed">-->
|
||||
<!-- <input-->
|
||||
<!-- type="checkbox"-->
|
||||
<!-- bind:checked={hideCompleted}-->
|
||||
<!-- />-->
|
||||
<!-- Hide Completed Tasks-->
|
||||
<!-- </label>-->
|
||||
|
||||
<!-- <BlazeTemplate template="loginButtons"/>-->
|
||||
{#if !$currentUser}
|
||||
<button type="button" on:click={performLogin}>Login</button>
|
||||
{:else}
|
||||
<button type="button" on:click={performLogout}>Logout</button>
|
||||
{/if}
|
||||
|
||||
<!--{#if $currentUser}-->
|
||||
<!-- <form class="new-task" on:submit|preventDefault={handleSubmit}>-->
|
||||
<!-- <input-->
|
||||
<!-- type="text"-->
|
||||
<!-- placeholder="Type to add new tasks"-->
|
||||
<!-- bind:value={newTask}-->
|
||||
<!-- />-->
|
||||
<!-- </form>-->
|
||||
<!--{/if}-->
|
||||
</header>
|
||||
<ul>
|
||||
<!--{#each tasks as task}-->
|
||||
<!-- <Task-->
|
||||
<!-- key={task._id}-->
|
||||
<!-- task={task}-->
|
||||
<!-- />-->
|
||||
<!--{/each}-->
|
||||
</ul>
|
||||
</div>
|
||||
<Route path="/">
|
||||
<div class="container">
|
||||
<header class="row">
|
||||
<nav class="col-12 center">
|
||||
<h1>AVUSD District Central</h1>
|
||||
<a href="/">Home</a>
|
||||
{#if isAdmin}
|
||||
<a href="/chromebooks">Chromebooks</a>
|
||||
{/if}
|
||||
</nav>
|
||||
{#if !$currentUser}
|
||||
<button type="button" on:click={performLogin}>Login</button>
|
||||
{:else}
|
||||
<button type="button" on:click={performLogout}>Logout</button>
|
||||
{/if}
|
||||
</header>
|
||||
</div>
|
||||
</Route>
|
||||
{#if $currentUser}
|
||||
{#if isAdmin}
|
||||
<Route path="/chromebooks/*">
|
||||
<Chromebooks/>
|
||||
</Route>
|
||||
{:else}
|
||||
<Route fallback redirect="/"/>
|
||||
{/if}
|
||||
{:else}
|
||||
<div className="container" style="background-color: #4d4242">
|
||||
<div class="maincontainer row">
|
||||
<div class="bat">
|
||||
<img class="wing leftwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
<img class="body"
|
||||
src="/images/forbidden/bat-body.png" alt="bat">
|
||||
<img class="wing rightwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
</div>
|
||||
<div class="bat">
|
||||
<img class="wing leftwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
<img class="body"
|
||||
src="/images/forbidden/bat-body.png" alt="bat">
|
||||
<img class="wing rightwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
</div>
|
||||
<div class="bat">
|
||||
<img class="wing leftwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
<img class="body"
|
||||
src="/images/forbidden/bat-body.png" alt="bat">
|
||||
<img class="wing rightwing"
|
||||
src="/images/forbidden/bat-wing.png">
|
||||
</div>
|
||||
<img class="foregroundimg" src="/images/forbidden/HauntedHouseForeground.png" alt="haunted house">
|
||||
|
||||
</div>
|
||||
<h1 class="errorcode">ERROR 403</h1>
|
||||
<div class="errortext">This area is forbidden. Turn back now!</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
41
imports/ui/ChromebookScan.svelte
Normal file
41
imports/ui/ChromebookScan.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<style>
|
||||
.error {
|
||||
color: darkred;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import {Html5QrcodeScanner} from "html5-qrcode";
|
||||
|
||||
function onScanSuccess(decodedText, decodedResult) {
|
||||
console.log('Code matched ' + decodedResult);
|
||||
document.getElementById("log").prepend(decodedText);
|
||||
}
|
||||
|
||||
function onScanFailure(error) {
|
||||
}
|
||||
|
||||
function scanner() {
|
||||
let html5QrcodeScanner = new Html5QrcodeScanner("reader", {
|
||||
fps: 10,
|
||||
qrbox: {width: 250, height: 250}
|
||||
}, /* verbose */ false);
|
||||
|
||||
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div className="container">
|
||||
<div class="row">
|
||||
Hello World!
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div use:scanner id="reader" width="300px"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div id="log" class="col-12">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
34
imports/ui/Chromebooks.svelte
Normal file
34
imports/ui/Chromebooks.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
<style>
|
||||
nav {
|
||||
font-size: 2rem;
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
color: green;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {Route} from 'tinro';
|
||||
import ChromebookScan from './ChromebookScan.svelte';
|
||||
</script>
|
||||
|
||||
<Route path="/">
|
||||
<div className="container">
|
||||
<div class="row">
|
||||
<nav class="col-12 center">
|
||||
<h1>Chromebook Management</h1>
|
||||
<a href="chromebooks/scan">Scan A Chromebook</a>
|
||||
<a href="chromebooks/byStudent">Chromebook History By Student</a>
|
||||
<a href="chromebooks/byChromebook">Chromebook History By Chromebook</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
<Route path="/scan">
|
||||
<ChromebookScan/>
|
||||
</Route>
|
||||
<Route path="/byStudent">TODO: Use student email to look up Chromebook history</Route>
|
||||
<Route path="/byChromebook">TODO: Use chromebook ID (or picture of ID) to look up Chromebook history</Route>
|
||||
12
imports/ui/ProcessLaptops.svelte
Normal file
12
imports/ui/ProcessLaptops.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {onMount} from 'svelte';
|
||||
|
||||
onMount(async () => {
|
||||
// Meteor.subscribe('records');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<h1>Process Laptops</h1>
|
||||
</div>
|
||||
2
imports/util/JsBarcode.min.js
vendored
Normal file
2
imports/util/JsBarcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2363
imports/util/qrious.js
Normal file
2363
imports/util/qrious.js
Normal file
File diff suppressed because it is too large
Load Diff
1
imports/util/qrious.js.map
Normal file
1
imports/util/qrious.js.map
Normal file
File diff suppressed because one or more lines are too long
6
imports/util/qrious.min.js
vendored
Normal file
6
imports/util/qrious.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
imports/util/qrious.min.js.map
Normal file
1
imports/util/qrious.min.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user