Added new models. Major changes to the table widget. Still has problems with the editing in the table widget.
This commit is contained in:
@@ -361,6 +361,7 @@ html {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
@@ -378,4 +379,8 @@ html {
|
||||
font-feature-settings: "liga";
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 48;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=main.css.map */
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["simple-grid.sass","app.sass","material-icons.sass"],"names":[],"mappings":"AAAQ;AAER;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;AAEA;EACC;EACA;EACA;;;AAED;AAEA;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;AAEA;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;AAEA;EAEC;EACA;EACA;;;AAED;EACC;;AAGA;EACC;EACA;EACA;;;AAEF;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;AACC;EAEA;IACC;;;AAEF;AACC;EAEA;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;AAEF;AACC;EAEA;IACC;IACA;;;ACzLF;AACA;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAQD;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;IACC;;;AAEF;EACC;IACC;;;EAED;IACC;IACA;;;EAED;IACC;;;EAED;IACC;;;AC3GF;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA","file":"main.css"}
|
||||
{"version":3,"sourceRoot":"","sources":["simple-grid.sass","app.sass","material-icons.sass"],"names":[],"mappings":"AAAQ;AAER;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;AAEA;EACC;EACA;EACA;;;AAED;AAEA;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;AAEA;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;AAEA;EAEC;EACA;EACA;;;AAED;EACC;;AAGA;EACC;EACA;EACA;;;AAEF;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;AACC;EAEA;IACC;;;AAEF;AACC;EAEA;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;EAED;IACC;;;AAEF;AACC;EAEA;IACC;IACA;;;ACzLF;AACA;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAQD;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;IACC;;;AAEF;EACC;IACC;;;EAED;IACC;IACA;;;EAED;IACC;;;EAED;IACC;;;ACzGF;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;AACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;EACA;AACA;EACA;AACA;EACA;AACA;EACA;;;AAED;EACC","file":"main.css"}
|
||||
@@ -1,3 +1,5 @@
|
||||
// https://github.com/google/material-design-icons
|
||||
|
||||
@font-face
|
||||
font-family: 'Material Icons'
|
||||
font-style: normal
|
||||
@@ -9,7 +11,8 @@
|
||||
font-family: 'Material Icons'
|
||||
font-weight: normal
|
||||
font-style: normal
|
||||
font-size: 24px /* Preferred icon size */
|
||||
font-size: 24px
|
||||
/* Preferred icon size */
|
||||
display: inline-block
|
||||
line-height: 1
|
||||
text-transform: none
|
||||
@@ -17,14 +20,14 @@
|
||||
word-wrap: normal
|
||||
white-space: nowrap
|
||||
direction: ltr
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga'
|
||||
|
||||
.material-symbols-outlined
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 48
|
||||
|
||||
13
client/theme/smui-dark.css
Normal file
13
client/theme/smui-dark.css
Normal file
File diff suppressed because one or more lines are too long
16476
client/theme/smui.css
16476
client/theme/smui.css
File diff suppressed because one or more lines are too long
@@ -1,3 +1,6 @@
|
||||
import "./users.js";
|
||||
import "./data-collection.js";
|
||||
import "./admin.js";
|
||||
import "./students.js";
|
||||
import "./rooms.js";
|
||||
import "./sites.js";
|
||||
|
||||
24
imports/api/rooms.js
Normal file
24
imports/api/rooms.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import {Meteor} from "meteor/meteor";
|
||||
|
||||
export const Rooms = new Mongo.Collection('rooms');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// This code only runs on the server
|
||||
Meteor.publish('rooms', function(siteId) {
|
||||
return Rooms.find({siteId});
|
||||
});
|
||||
}
|
||||
Meteor.methods({
|
||||
'rooms.add'(name, siteId) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Rooms.insert({name, siteId});
|
||||
}
|
||||
},
|
||||
'rooms.remove'(_id) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
//TODO: Need to first verify there are no checked out assets to the room.
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
37
imports/api/sites.js
Normal file
37
imports/api/sites.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {Students} from "./students";
|
||||
import {Rooms} from "./rooms";
|
||||
|
||||
export const Sites = new Mongo.Collection('sites');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// This code only runs on the server
|
||||
Meteor.publish('sites', function() {
|
||||
return Sites.find({});
|
||||
});
|
||||
}
|
||||
Meteor.methods({
|
||||
'sites.update'(_id, name) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Sites.update({_id}, {$set: {name}});
|
||||
}
|
||||
},
|
||||
'sites.add'(name) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Sites.insert({name});
|
||||
}
|
||||
},
|
||||
'sites.remove'(_id) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
let site = Sites.find({_id});
|
||||
|
||||
if(site) {
|
||||
//Clear any site references in student/room entries.
|
||||
Students.update({siteId: _id}, {$unset: {siteId: 1}});
|
||||
Rooms.update({siteId: _id}, {$unset: {siteId: 1}});
|
||||
Sites.remove({_id});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
66
imports/api/students.js
Normal file
66
imports/api/students.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import {Sites} from "./sites";
|
||||
|
||||
export const Students = new Mongo.Collection('students');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// This code only runs on the server
|
||||
Meteor.publish('students', function(siteId) {
|
||||
return Students.find({siteId});
|
||||
});
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
/**
|
||||
* Expects the CSV string to contain comma delimited data in the form:
|
||||
* email, student ID, first name, last name, grade, first name alias, last name alias
|
||||
*
|
||||
* The query in Aeries is: `LIST STU SEM ID FN LN GR FNA LNA`.
|
||||
* The query in SQL is: `SELECT [STU].[SEM] AS [StuEmail], [STU].[ID] AS [Student ID], STU.FN AS [First Name], STU.LN AS [Last Name], [STU].[GR] AS [Grade], [STU].[FNA] AS [First Name Alias], [STU].[LNA] AS [Last Name Alias] FROM (SELECT [STU].* FROM STU WHERE [STU].DEL = 0) STU WHERE ( [STU].SC = 5) ORDER BY [STU].[LN], [STU].[FN];`.
|
||||
* Run the query in Aeries as a `Report`, select TXT, and upload here.
|
||||
*
|
||||
* Aeries adds a header per 'page' of data (I think 35 entries per page).
|
||||
* Example:
|
||||
* Anderson Valley Jr/Sr High School,6/11/2022
|
||||
* 2021-2022,Page 1
|
||||
* StuEmail,Student ID,First Name,Last Name,Grade,First Name Alias,Last Name Alias
|
||||
*/
|
||||
'students.loadCsv'(csv, siteId) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
check(csv, String);
|
||||
check(siteId, String);
|
||||
|
||||
let site = Sites.find({_id: siteId});
|
||||
|
||||
if(site) {
|
||||
let lines = csv.split(/\r?\n/);
|
||||
let pageHeader = lines[0];
|
||||
let skip;
|
||||
|
||||
for (const line of lines) {
|
||||
if (skip > 0) skip--;
|
||||
else if (line === pageHeader) {
|
||||
skip = 2;
|
||||
} else {
|
||||
let values = line.split(/\s*,\s*/);
|
||||
let email = values[0];
|
||||
let id = values[1];
|
||||
let firstName = values[2];
|
||||
let lastName = values[3];
|
||||
let grade = values[4];
|
||||
let firstNameAlias = values[5];
|
||||
let lastNameAlias = values[6];
|
||||
let student = {email, id, firstName, lastName, grade, firstNameAlias, lastNameAlias};
|
||||
|
||||
console.log(student);
|
||||
|
||||
//TODO: Find existing student. Update student, and move them to the new site.
|
||||
//TODO: Create student if none exists.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,8 +1,124 @@
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {onMount} from "svelte";
|
||||
import {Sites} from "../api/sites";
|
||||
import GridTable from "./GridTable.svelte";
|
||||
import {writable} from "svelte/store";
|
||||
import TextField from '@smui/textfield';
|
||||
import HelperText from '@smui/textfield/helper-text';
|
||||
|
||||
onMount(async () => {
|
||||
Meteor.subscribe('sites');
|
||||
});
|
||||
|
||||
const fixRecords = () => {Meteor.call("admin.fixRecords");}
|
||||
|
||||
const submitForm = () => {
|
||||
Meteor.call('students.loadCsv');
|
||||
}
|
||||
let files;
|
||||
|
||||
const siteColumns = [
|
||||
{
|
||||
key: "_id",
|
||||
title: "ID",
|
||||
value: v => v._id,
|
||||
minWidth: 20,
|
||||
weight: 1,
|
||||
cls: "id",
|
||||
}, {
|
||||
key: "name",
|
||||
title: "Name",
|
||||
value: v => v.name,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "name",
|
||||
},
|
||||
];
|
||||
const actions = {
|
||||
title: "Actions",
|
||||
headerWidgets: [
|
||||
{icon: "add_box", action: () => {editedSite.set({name: ""});}, tooltip: "Add a new Site."}
|
||||
],
|
||||
rowWidgets: [
|
||||
{icon: "add_circle", action: (v) => {editedSite.set(v)}},
|
||||
{icon: "delete", action: (v) => {deleteSite(v)}}
|
||||
],
|
||||
};
|
||||
const deleteSite = site => {
|
||||
//TODO:
|
||||
};
|
||||
let editedSite = writable(null);
|
||||
let sites = Sites.find({});
|
||||
|
||||
const applySiteChanges = () => {
|
||||
if($editedSite._id)
|
||||
Meteor.call("sites.update", $editedSite._id,$editedSite.name);
|
||||
else
|
||||
Meteor.call("sites.add", $editedSite.name);
|
||||
editedSite.set(null);
|
||||
}
|
||||
const rejectSiteChanges = () => {
|
||||
editedSite.set(null);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<button type="button" on:click={fixRecords}>Fix Records</button>
|
||||
<GridTable bind:rows={sites} columns="{siteColumns}" actions="{actions}" rowKey="{(user) => {return user._id}}" bind:edited="{editedSite}">
|
||||
{#if $editedSite}
|
||||
<div class="editorContainer">
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={$editedSite.name} label="Name">
|
||||
<HelperText slot="helper">Provide a unique name for the site.</HelperText>
|
||||
</TextField>
|
||||
</div>
|
||||
|
||||
<button type="button" style="grid-column: 2/span 1;" class="button accept-button material-icons material-symbols-outlined" on:click={applySiteChanges}>
|
||||
check
|
||||
</button>
|
||||
<button type="button" style="grid-column: 3/span 1;" class="button reject-button material-icons material-symbols-outlined" on:click={rejectSiteChanges}>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</GridTable>
|
||||
|
||||
<!--{#each sites as site}-->
|
||||
<!-- <div>{site.name}</div>-->
|
||||
<!--{/each}-->
|
||||
<!-- <button type="button" on:click={fixRecords}>Fix Records</button>-->
|
||||
<form on:submit={submitForm}>
|
||||
<input type="file" bind:files={files}/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.editorContainer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
||||
}
|
||||
.accept-button, .reject-button {
|
||||
font-size: .8rem;
|
||||
padding: .6rem;
|
||||
margin: auto;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
font-weight: 800;
|
||||
alignment: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.accept-button {
|
||||
background-color: rgba(61, 148, 61, 0.91);
|
||||
}
|
||||
.reject-button {
|
||||
background-color: rgba(176, 64, 64, 0.61);
|
||||
}
|
||||
.accept-button:hover {
|
||||
background-color: rgb(61, 148, 61);
|
||||
}
|
||||
.reject-button:hover {
|
||||
background-color: rgb(176, 64, 64);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
import ChromebookScan from './ChromebookScan.svelte';
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import TextField from '@smui/textfield';
|
||||
import HelperText from '@smui/textfield/helper-text';
|
||||
import Icon from '@smui/textfield/icon';
|
||||
import { Icon as CommonIcon } from '@smui/common';
|
||||
|
||||
$: serialInput = null;
|
||||
$: emailInput = null;
|
||||
@@ -150,7 +153,12 @@
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<TextField type="text" bind:value={emailInput} label="Email">
|
||||
<TextField type="text" bind:value={emailInput} on:keypress={(e) => {if(e.keyCode === 13) emailSearch()}} label="Email">
|
||||
<svelte:fragment slot="label">
|
||||
<CommonIcon class="material-icons" style="font-size: 1em; line-height: normal; vertical-align: top;">email</CommonIcon> Email
|
||||
</svelte:fragment>
|
||||
<Icon class="material-icons" slot="trailingIcon">search</Icon>
|
||||
<HelperText slot="helper">Type any part of an email address.</HelperText>
|
||||
</TextField>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
<script>
|
||||
export let rows;
|
||||
export let columns;
|
||||
export let rowKey;
|
||||
export let rowKey; //Must only be null/undefined if the row is a new object (not associated with a row in the table). Should not change.
|
||||
export let edited;
|
||||
export let actions;
|
||||
|
||||
// Setup a width for each column.
|
||||
columns.forEach(column => {
|
||||
let min = column.minWidth ? Math.max(10, column.minWidth) : 10;
|
||||
let weight = column.weight ? Math.max(1, column.weight) : 1;
|
||||
column.width = 'minmax(' + min + 'px, ' + weight + 'fr)';
|
||||
column.isActions = false;
|
||||
});
|
||||
// Add the actions column to the end.
|
||||
// TODO: Allow it to be positioned.
|
||||
if(actions) {
|
||||
// TODO: make this fixed width based on the possible widget widths.
|
||||
actions.width = 'minmax(10px, 1fr)';
|
||||
actions.isActions = true;
|
||||
columns[columns.length] = actions;
|
||||
}
|
||||
// Collect the column widths for the layout.
|
||||
let gridTemplateColumns = columns.map(({width}) => width).join(' ');
|
||||
|
||||
// Resize column code.
|
||||
let headerBeingResized = null;
|
||||
let horizontalScrollOffset = 0;
|
||||
const initResize = ({target}) => {
|
||||
@@ -46,6 +58,7 @@
|
||||
} catch(e) {console.log(e);}
|
||||
}
|
||||
|
||||
// Select row code.
|
||||
let selectedRowElement = null;
|
||||
const selectRow = (e, row) => {
|
||||
let element = e.target;
|
||||
@@ -60,12 +73,44 @@
|
||||
element.classList.add('selected');
|
||||
}
|
||||
|
||||
// Edit row code.
|
||||
let editorContainer;
|
||||
let editorTable;
|
||||
// Listen for changes to the edited variable. Move the editor row and display it when there is a value.
|
||||
// Relies on the table row being hidden when the edited value matches the row's value.
|
||||
$: {
|
||||
if(editorContainer) {
|
||||
if($edited) {
|
||||
let id = rowKey($edited);
|
||||
let hiddenRow = editorTable.querySelector('tbody tr[data-key="' + id + '"]');
|
||||
|
||||
if(!hiddenRow) {
|
||||
let body = editorTable.querySelector('tbody');
|
||||
body.firstChild ? body.insertBefore(editorContainer, body.firstChild) : body.appendChild(editorContainer);
|
||||
editorContainer.classList.remove('hidden');
|
||||
}
|
||||
else {
|
||||
//let editor = hiddenRow.querySelector('.editor');
|
||||
let body = editorTable.querySelector('tbody');
|
||||
let next = hiddenRow.nextSibling;
|
||||
//editor.appendChild(editorContainer);
|
||||
next ? body.insertBefore(editorContainer, next) : body.appendChild(editorContainer);
|
||||
editorContainer.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Edited cleared");
|
||||
editorContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
const editRow = (e, row) => {
|
||||
let element = e.target;
|
||||
while(element && element.nodeName !== "TR") element = element.parentNode;
|
||||
let editor = element.querySelector('.editor');
|
||||
|
||||
isNew = false;
|
||||
// Save the edited row so the editor has access to it.
|
||||
$edited = row;
|
||||
|
||||
@@ -74,32 +119,60 @@
|
||||
}
|
||||
editorContainer.classList.remove('hidden');
|
||||
}
|
||||
const newRow = () => {
|
||||
isNew = true;
|
||||
newEditor.appendChild(editorContainer);
|
||||
editorContainer.classList.remove('hidden');
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
|
||||
<div bind:this={editorContainer} class="hidden"><slot>Slot</slot></div>
|
||||
<table style="--grid-template-columns: {gridTemplateColumns}">
|
||||
<div bind:this={editorContainer} class="editor2 hidden"><slot>Slot</slot></div>
|
||||
<table bind:this={editorTable} style="--grid-template-columns: {gridTemplateColumns}">
|
||||
<thead>
|
||||
<tr>
|
||||
{#each columns as column}
|
||||
{#if column.isActions}
|
||||
<th bind:this={column.element}>{column.title}
|
||||
{#each column.headerWidgets as widget}
|
||||
<span class="material-icons material-symbols-outlined" on:click={widget.action} title="{widget.tooltip}">{widget.icon}</span>
|
||||
{/each}
|
||||
</th>
|
||||
{:else}
|
||||
<th bind:this={column.element}>{column.title} <span class="resize-handle" on:mousedown={initResize}></span></th>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each $rows as row (rowKey(row))}
|
||||
<!-- data-key="{rowKey(row)}"-->
|
||||
<tr class:hidden={row === $edited} on:mousedown={(e) => selectRow(e, row)} on:dblclick={(e) => editRow(e, row)}>
|
||||
<!-- on:dblclick={(e) => editRow(e, row)} -->
|
||||
<tr data-key="{rowKey(row)}" class:hidden={row === $edited} on:mousedown={(e) => selectRow(e, row)} on:dblclick={(e) => {$edited = row}}>
|
||||
{#each columns as column}
|
||||
<td>{column.value(row)}</td>
|
||||
{#if column.isActions}
|
||||
<td>
|
||||
{#each column.rowWidgets as widget}
|
||||
<span class="material-icons material-symbols-outlined" on:click={widget.action(row)}>{widget.icon}</span>
|
||||
{/each}
|
||||
</td>
|
||||
{:else}
|
||||
<td>{column.value(row)}</td>
|
||||
{/if}
|
||||
{/each}
|
||||
<td class="editor"></td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<button on:click={() => {$edited = null}} type="button">Stop Editing</button>
|
||||
<!--<button on:click={() => {$edited = null}} type="button">Stop Editing</button>-->
|
||||
|
||||
<style>
|
||||
.material-icons {
|
||||
cursor: pointer;
|
||||
}
|
||||
thead .material-icons {
|
||||
color: white;
|
||||
}
|
||||
table {
|
||||
width: auto;
|
||||
-webkit-box-flex: 1;
|
||||
@@ -122,10 +195,10 @@
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #5cb85c;
|
||||
background: #3f4f3f;
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
font-size: 1.1rem;
|
||||
font-size: 1rem;
|
||||
color: white;
|
||||
position: relative;
|
||||
}
|
||||
@@ -163,7 +236,7 @@
|
||||
background-color: yellow;
|
||||
}
|
||||
.editor {
|
||||
grid-column: 1 / 4;
|
||||
grid-column: 1 / -1;
|
||||
display: none;
|
||||
}
|
||||
/*:global(td.hidden) {*/
|
||||
@@ -178,4 +251,8 @@
|
||||
:global(div.hidden) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.editor2 {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,10 +43,8 @@
|
||||
//TODO: Setup the editor for the given row.
|
||||
}
|
||||
let edited = writable(null);
|
||||
let editedPermissions = null;
|
||||
|
||||
$: rows = Meteor.users.find({});
|
||||
|
||||
let editedPermissions = null;
|
||||
edited.subscribe((value) => {
|
||||
if(value) {
|
||||
editedPermissions = {
|
||||
@@ -82,7 +80,7 @@
|
||||
Loading...
|
||||
{:then allUsers}
|
||||
<GridTable bind:rows={rows} columns="{columns}" rowKey="{getRowKey}" bind:edited="{edited}">
|
||||
{#if editedPermissions}
|
||||
{#if editedPermissions && $edited && $edited.profile}
|
||||
<div class="editorContainer">
|
||||
<label style="grid-column: 1/4; font-weight: 800; border-bottom: 2px solid #888; margin-bottom: 0.5rem">{$edited.profile.name}</label>
|
||||
<label class="checkbox" style="grid-column: 1/4;"><input type="checkbox" bind:checked="{editedPermissions.isAdmin}" style="--form-control-color: black"/> Administrator</label>
|
||||
|
||||
46
package-lock.json
generated
46
package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"ws": "^8.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@smui/common": "^6.0.0-beta.16",
|
||||
"@smui/textfield": "^6.0.0-beta.16",
|
||||
"chai": "^4.2.0",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
@@ -2695,22 +2696,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.75.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.5.tgz",
|
||||
"integrity": "sha512-JzNlJZDison3o2mOxVmb44Oz7t74EfSd1SQrplQk0wSaXV7uLQXtVdHbxlcT3w+8tZ1TL4r/eLfc7nAbz38BBA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-plugin-css-only": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-3.1.0.tgz",
|
||||
@@ -3007,19 +2992,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz",
|
||||
@@ -5530,16 +5502,6 @@
|
||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
|
||||
"dev": true
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.75.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.5.tgz",
|
||||
"integrity": "sha512-JzNlJZDison3o2mOxVmb44Oz7t74EfSd1SQrplQk0wSaXV7uLQXtVdHbxlcT3w+8tZ1TL4r/eLfc7nAbz38BBA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-css-only": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-3.1.0.tgz",
|
||||
@@ -5765,12 +5727,6 @@
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz",
|
||||
"integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==",
|
||||
"peer": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"css": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@smui/common": "^6.0.0-beta.16",
|
||||
"@smui/textfield": "^6.0.0-beta.16",
|
||||
"chai": "^4.2.0",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
|
||||
Reference in New Issue
Block a user