Added new models. Major changes to the table widget. Still has problems with the editing in the table widget.

This commit is contained in:
2022-06-13 07:42:26 -07:00
parent 11fbfcfac0
commit 69ca0d5eb6
15 changed files with 417 additions and 16578 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,73 +119,101 @@
}
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}
<th bind:this={column.element}>{column.title} <span class="resize-handle" on:mousedown={initResize}></span></th>
{#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;
flex: 1;
display: grid;
border-collapse: collapse;
grid-template-columns: var(--grid-template-columns);
width: auto;
-webkit-box-flex: 1;
flex: 1;
display: grid;
border-collapse: collapse;
grid-template-columns: var(--grid-template-columns);
}
thead, tbody, tr {
display: contents;
}
th, td {
padding: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
padding: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
}
th {
position: -webkit-sticky;
position: sticky;
top: 0;
background: #5cb85c;
text-align: left;
font-weight: normal;
font-size: 1.1rem;
color: white;
position: relative;
position: -webkit-sticky;
position: sticky;
top: 0;
background: #3f4f3f;
text-align: left;
font-weight: normal;
font-size: 1rem;
color: white;
position: relative;
}
th:last-child {
border: 0;
border: 0;
}
.resize-handle {
position: absolute;
top: 0;
right: 0;
bottom: 0;
background: black;
opacity: 0;
width: 3px;
cursor: col-resize;
position: absolute;
top: 0;
right: 0;
bottom: 0;
background: black;
opacity: 0;
width: 3px;
cursor: col-resize;
}
th:last-child .resize-handle {
display: none;
@@ -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>

View File

@@ -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>