Skip to content

Commit

Permalink
started with inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
mono424 committed Sep 21, 2023
1 parent cc9f2d0 commit 34248ac
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 26 deletions.
4 changes: 4 additions & 0 deletions web/assets/init-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ document.addEventListener("alpine:init", () => {
*
* Modifiers:
* - "text": When provided, the directive will also update the element's innerText.
* - "value": When provided, the directive will also update the element's value.
*
* Custom Events:
* - "csupdate": Custom event triggered when the change set is updated.
Expand All @@ -187,6 +188,9 @@ document.addEventListener("alpine:init", () => {
if (modifiers.includes("text")) {
el.innerText = `${value}`;
}
if (modifiers.includes("value")) {
el.value = value;
}
el.dispatchEvent(new CustomEvent(nativeEventName, { detail: { changeSet, value } }));
};

Expand Down
35 changes: 35 additions & 0 deletions web/template/partial/course/manage/lecture-management-card.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,41 @@
</div>
</template>
</article>

<header class="border-b dark:border-gray-600 w-full">
<h6 class="text-sm text-5 font-light">Date and Time</h6>
</header>
<article class="w-full mb-2 flex"
x-data="{ startId: $id('date-input'), endId: $id('date-input') }"
>
<label :for="startId" class="grow mr-2">
<span class="text-sm text-5">Start</span>
<input class="tl-input"
name="newStartDate"
:id="startId"
x-change-set-listen="changeSet.startDate"
x-on-change-set-update="$el._flatpickr.setDate(flatpickr.formatDate(changeSet.get().startDate, 'Y-m-d H:i'))"
x-init="flatpickr($el, {enableTime: true, time_24hr: true, altInput: true, altFormat:'Y-m-d H:i', dateFormat:'Z', allowInput: true, onChange: (date) => changeSet.patch('start', date[0].toString()) })"
/>
</label>
<template x-if="!lectureData.premiere && !lectureData.vodup">
<label :for="endId">
<span class="text-sm text-5">End</span>
<span class="text-sm font-light opacity-75"
x-change-set-listen.text="changeSet.durationFormatted"
>
</span>
<input class="tl-input"
name="newEndTime"
:id="endId"
x-change-set-listen="changeSet.endDate"
x-on-change-set-update="$el._flatpickr.setDate(flatpickr.formatDate(changeSet.get().endDate, 'H:i'))"
x-init="flatpickr($el, {enableTime: true, noCalendar: true, altInput: true, altFormat: 'H:i', time_24hr: true, allowInput: true, onChange: (date) => changeSet.patch('end', date.toString()) })"
/>
</label>
</template>
</article>

<header class="border-b dark:border-gray-600 w-full">
<h6 class="text-sm text-5 font-light">Title and Description</h6>
</header>
Expand Down
3 changes: 2 additions & 1 deletion web/ts/api/admin-lecture-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,9 @@ export interface Lecture {
startDateFormatted: string;
startTimeFormatted: string;
endDate: Date;
endDateFormatted: string;
endTimeFormatted: string;
duration: number;
durationFormatted: string;

// Clientside pseudo fields
newCombinedVideo: File | null;
Expand Down
78 changes: 67 additions & 11 deletions web/ts/change-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ export interface DirtyState {
dirtyKeys: string[];
}

export interface ChangeSetOptions<T> {
comparator?: (key: string, a: T, b: T) => boolean,
updateTransformer?: ComputedProperties<T>,
onUpdate?: (changeState: T, dirtyState: DirtyState) => void,
};

/**
* ## ChangeSet Class
*
Expand Down Expand Up @@ -57,16 +63,19 @@ export class ChangeSet<T> {
private changeState: T;
private readonly comparator?: PropertyComparator<T>;
private onUpdate: ((changeState: T, dirtyState: DirtyState) => void)[];
private readonly changeStateTransformer?: ((changeState: T) => T);
private readonly stateTransformer?: ((changeState: T) => T);

constructor(
state: T,
comparator?: (key: string, a: T, b: T) => boolean,
onUpdate?: (changeState: T, dirtyState: DirtyState) => void,
{ comparator, updateTransformer, onUpdate }: ChangeSetOptions<T> = {}
) {
this.state = state;
this.onUpdate = onUpdate ? [onUpdate] : [];
this.changeStateTransformer = updateTransformer !== undefined ? updateTransformer.create() : undefined;
this.stateTransformer = updateTransformer !== undefined ? updateTransformer.create() : undefined;
this.comparator = comparator;
this.reset();
this.init();
}

/**
Expand All @@ -90,7 +99,7 @@ export class ChangeSet<T> {
* @param key key to return
* @param lastCommittedState if set to true, value of the last committed state is returned
*/
getValue(key: string, { lastCommittedState = false }): T {
getValue(key: string, { lastCommittedState = false } = {}): T {
if (lastCommittedState) {
return this.state[key];
}
Expand All @@ -110,7 +119,7 @@ export class ChangeSet<T> {
*/
set(val: T) {
this.changeState = { ...val };
this.dispatchUpdate();
this.dispatchUpdate(false);
}

/**
Expand All @@ -125,7 +134,7 @@ export class ChangeSet<T> {
if (isCommitted) {
this.state = { ...this.state, [key]: val };
}
this.dispatchUpdate();
this.dispatchUpdate(isCommitted);
}

/**
Expand All @@ -141,8 +150,7 @@ export class ChangeSet<T> {
this.changeState[key] = this.state[key];
}
}

this.dispatchUpdate();
this.dispatchUpdate(true);
}

/**
Expand All @@ -154,15 +162,23 @@ export class ChangeSet<T> {
this.changeState[key] = this.state[key];
}
this.state = { ...this.changeState };
this.dispatchUpdate();
this.dispatchUpdate(true);
}

/**
* Init new state
*/
init(): void {
this.changeState = { ...this.state };
this.dispatchUpdate(true);
}

/**
* Resets the change state to the state. Change state is the most current state afterwards.
*/
reset(): void {
this.changeState = { ...this.state };
this.dispatchUpdate();
this.dispatchUpdate(false);
}

/**
Expand Down Expand Up @@ -209,8 +225,17 @@ export class ChangeSet<T> {

/**
* Executes all onUpdate listeners
* @param stateChanged if state changed, state computed values are recalculated
*/
dispatchUpdate() {
dispatchUpdate(stateChanged: boolean) {
if (stateChanged && this.stateTransformer) {
this.state = this.stateTransformer(this.state);
}

if (this.changeStateTransformer) {
this.changeState = this.changeStateTransformer(this.changeState);
}

if (this.onUpdate.length > 0) {
const dirtyKeys = this.changedKeys();
for (const onUpdate of this.onUpdate) {
Expand Down Expand Up @@ -257,3 +282,34 @@ export function comparatorPipeline<T>(list: PropertyComparator<T>[]): PropertyCo
return null;
};
}

export type ComputedPropertyTransformer<T> = ((state: T) => T);
export type ComputedPropertySubTransformer<T> = ((state: T, oldState: T) => T);

export class ComputedProperties<T> {
private readonly computed: ComputedPropertySubTransformer<T>[];

constructor(computed: ComputedPropertySubTransformer<T>[]) {
this.computed = computed;
}

create(): ComputedPropertyTransformer<T> {
let oldState: T|null = null;
return (state: T) => {
for (const transformer of this.computed) {
state = transformer(state, oldState);
}
oldState = {...state};
return state;
};
}
}

export function computedProperty<T, R>(key: string, updater: (changeState: T, old: T|null) => R, deps: string[] = []): ComputedPropertySubTransformer<T> {
return (state: T, oldState: T|null) => {
if (oldState == null || deps.length == 0 || deps.some((k) => oldState[k] !== state[k])) {
state[key] = updater(state, oldState);
}
return state;
};
}
12 changes: 2 additions & 10 deletions web/ts/data-store/admin-lecture-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
import { FileType } from "../edit-course";
import { PostFormDataListener } from "../utilities/fetch-wrappers";

const dateFormatOptions: Intl.DateTimeFormatOptions = {
export const dateFormatOptions: Intl.DateTimeFormatOptions = {
weekday: "long",
year: "numeric",
month: "short",
day: "2-digit",
};
const timeFormatOptions: Intl.DateTimeFormatOptions = {
export const timeFormatOptions: Intl.DateTimeFormatOptions = {
hour: "2-digit",
minute: "2-digit",
};
Expand All @@ -36,14 +36,6 @@ export class AdminLectureListProvider extends StreamableMapProvider<number, Lect

s.videoSections = (s.videoSections ?? []).sort(videoSectionSort);

s.startDate = new Date(s.start);
s.startDateFormatted = s.startDate.toLocaleDateString("en-US", dateFormatOptions);
s.startTimeFormatted = s.startDate.toLocaleTimeString("en-US", timeFormatOptions);

s.endDate = new Date(s.end);
s.endDateFormatted = s.endDate.toLocaleDateString("en-US", dateFormatOptions);
s.endTimeFormatted = s.endDate.toLocaleTimeString("en-US", timeFormatOptions);

s.newCombinedVideo = null;
s.newPresentationVideo = null;
s.newCameraVideo = null;
Expand Down
65 changes: 61 additions & 4 deletions web/ts/edit-course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ import {
videoSectionSort,
videoSectionTimestamp,
} from "./api/admin-lecture-list";
import { ChangeSet, comparatorPipeline, ignoreKeys, singleProperty } from "./change-set";
import {
ChangeSet,
comparatorPipeline, ComputedProperties,
computedProperties,
computedProperty,
ignoreKeys,
singleProperty
} from "./change-set";
import { AlpineComponent } from "./components/alpine-component";
import { uploadFile } from "./utilities/fetch-wrappers";
import {dateFormatOptions, timeFormatOptions} from "./data-store/admin-lecture-list";

export enum UIEditMode {
none,
Expand Down Expand Up @@ -141,10 +149,42 @@ export function lectureEditor(lecture: Lecture): AlpineComponent {
}),
]);

const computedFields = new ComputedProperties([
computedProperty<Lecture, Date>("startDate", (changeSet) => {
return new Date(changeSet.start);
}, ["start"]),
computedProperty<Lecture, string>("startDateFormatted", (changeSet) => {
return changeSet.startDate.toLocaleDateString("en-US", dateFormatOptions);
}, ["start"]),
computedProperty<Lecture, string>("startTimeFormatted", (changeSet) => {
return changeSet.startDate.toLocaleDateString("en-US", timeFormatOptions);
}, ["start"]),
computedProperty<Lecture, Date>("endDate", (changeSet) => {
return new Date(changeSet.end);
}, ["end"]),
computedProperty<Lecture, string>("endTimeFormatted", (changeSet) => {
return changeSet.endDate.toLocaleTimeString("en-US", timeFormatOptions);
}, ["end"]),
computedProperty<Lecture, number>("duration", (changeSet) => {
// To ignore day differences
const normalizedEndDate = new Date(changeSet.startDate.getTime());
normalizedEndDate.setHours(changeSet.endDate.getHours())
normalizedEndDate.setMinutes(changeSet.endDate.getMinutes())
return normalizedEndDate.getTime() - changeSet.startDate.getTime();
}, ["start", "end"]),
computedProperty<Lecture, string>("durationFormatted", (changeSet) => {
return this.generateFormattedDuration(changeSet);
}, ["start", "end"]),
]);

// This tracks changes that are not saved yet
this.changeSet = new ChangeSet<Lecture>(lecture, customComparator, (data, dirtyState) => {
this.lectureData = data;
this.isDirty = dirtyState.isDirty;
this.changeSet = new ChangeSet<Lecture>(lecture, {
comparator: customComparator,
updateTransformer: computedFields,
onUpdate: (data, dirtyState) => {
this.lectureData = data;
this.isDirty = dirtyState.isDirty;
},
});

// This updates the state live in background
Expand Down Expand Up @@ -218,6 +258,23 @@ export function lectureEditor(lecture: Lecture): AlpineComponent {
DataStore.adminLectureList.deleteAttachment(this.lectureData.courseId, this.lectureData.lectureId, id);
},

generateFormattedDuration(lecture: Lecture): string {
if (lecture.duration <= 0) {
return "invalid";
}
const duration = lecture.duration / 1000 / 60
const hours = Math.floor(duration / 60);
const minutes = duration - hours * 60;
let res = "";
if (hours > 0) {
res += `${hours}h `;
}
if (minutes > 0) {
res += `${minutes}min`;
}
return res;
},

friendlySectionTimestamp(section: VideoSection): string {
return videoSectionFriendlyTimestamp(section);
},
Expand Down

0 comments on commit 34248ac

Please sign in to comment.