-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJavaScript.html
More file actions
179 lines (143 loc) · 8.87 KB
/
Copy pathJavaScript.html
File metadata and controls
179 lines (143 loc) · 8.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<script>
(function () {
// App object now focuses on state management and data operations.
const App = {
// --- STATE ---
tabId: `tab_${Date.now()}_${Math.random()}`,
isReadOnly: false,
schedules: {},
activeScheduleId: null,
activeMetadataTimestamp: null,
classrooms: [],
scheduleData: {},
tags: [], // This holds all tags for the current schedule
tagFilterTagify: null, // To hold the tagify instance for the filter
courseColorMap: {},
lastSyncTime: null,
scheduleLastModified: {},
loadingTimeout: null,
isConnecting: false,
activeInlineForm: null,
originalSourceListElement: null,
isDirty: false,
cleanStateSnapshot: '',
activeFilters: [],
currentUserEmail: '',
currentViewMode: localStorage.getItem('lastViewMode') || AppConfig.MODES.WEEK,
viewSortMode: ViewDecisionHelpers.resolveRestoredSort({
currentViewMode: localStorage.getItem('lastViewMode') || AppConfig.MODES.WEEK,
storedSort: localStorage.getItem('lastViewSortMode'),
dayMode: AppConfig.MODES.DAY
}),
currentDayIndex: (() => {
// If the initial view mode is 'day', set the index to today. Otherwise, default to Monday (0).
if ((localStorage.getItem('lastViewMode') || AppConfig.MODES.WEEK) === AppConfig.MODES.DAY) {
const today = new Date().getDay();
return (today === 0) ? 6 : today - 1; // Sunday is 0, we want it to be 6.
}
return 0;
})(),
nextUpcomingClassIds: new Set(),
pdfFontBase64: null,
// --- MODULES ---
modals: null,
historyModule: null,
ui: null,
interaction: null,
init: function () {
// Initialize all modules
this.modals = createModalModule(this);
this.historyModule = createHistoryModule(this);
this.ui = createUIModule(this);
this.interaction = createInteractionModule(this);
const fullEmail = typeof SCRIPT_USER_EMAIL !== 'undefined' ? SCRIPT_USER_EMAIL : '';
this.currentUserEmail = this.getShortUserName(fullEmail);
// The initial load logic is now split. We first load from local storage,
// then fetch from server, which will then call loadInitialSchedules again.
this.interaction.addEventListeners();
AppElements.versionBadge.textContent = `版本 ${AppConfig.APP_VERSION}`;
setTimeout(() => {
this.loadDataFromServer().catch(error => {
console.warn("Initial data load failed:", error.message);
});
}, 500);
// Set up a timer to periodically check for the next upcoming class.
setInterval(() => {
// First, always recalculate which classes are upcoming.
this.findNextUpcomingClasses();
// Then, if we are in a view where the upcoming status is relevant (today's day view),
// always re-render the table to reflect the passage of time.
const today = new Date();
const todayIndex = (today.getDay() === 0) ? 6 : today.getDay() - 1;
if (this.currentViewMode === AppConfig.MODES.DAY && this.currentDayIndex === todayIndex) {
this.ui.renderScheduleTable();
}
}, 60 * 1000); // Check every minute
// Ref: #67.4 — Heartbeat to keep the lock alive (10s interval reduces unnecessary API calls)
setInterval(() => this.refreshLockHeartbeat(), 10000);
},
// --- DATA & SCHEDULE LOGIC ---
// showFirstTimeScheduleSelector, handleEditClassroom — moved to ScheduleManager.js.html (Phase 1 PR6)
// loadInitialSchedules, loadSchedule, saveSchedulesToLocal — moved to ScheduleManager.js.html (Phase 1 PR6)
// handleAddSchedule, handleScheduleListClick, handleScheduleSelectChange — moved to ScheduleManager.js.html (Phase 1 PR6)
// loadAndApplyPersistedFilters, toggleAllFilterCheckboxes, applyFilters — moved to FilterEngine.js.html (Phase 1 PR4)
// clearAdvancedFilters, clearAllFilters — moved to FilterEngine.js.html (Phase 1 PR4)
applyTagFilters: async function () {
if (!this.tagFilterTagify) return;
const advancedFiltersExist = this.activeFilters.some(f => f.type !== 'tag');
const selectedTags = this.tagFilterTagify.value.map(tag => tag.value);
// If advanced filters are active, and the user is changing the primary tag filter, confirm first.
if (advancedFiltersExist && selectedTags.length > 0) {
const confirmed = await this.modals.showConfirm(
'變更主要標籤將會重設目前的進階篩選條件,確定要繼續嗎?'
);
if (!confirmed) {
// Revert the visual change in Tagify input to match the actual active filter state
const currentTags = this.activeFilters.filter(f => f.type === 'tag').map(f => f.value);
this.tagFilterTagify.loadOriginalValues(currentTags);
return;
}
}
// Set activeFilters to ONLY be the new tag filters, effectively clearing advanced filters.
this.activeFilters = selectedTags.map(tag => ({ type: 'tag', value: tag }));
localStorage.setItem('activeTagFilters', JSON.stringify(selectedTags));
this.modals.populateFilterModal();
this.ui.renderScheduleTable();
this.ui.updateAdvancedFilterButtonState();
this.ui.updateClearFilterButtonVisibility();
this.ui.updateClearAllFiltersButtonVisibility();
},
// loadVersions — moved to DataIO.js.html (Phase 1 PR5)
// handleLoadVersion — moved to DataIO.js.html (Phase 1 PR5)
// saveDataToLocal — moved to DataIO.js.html (Phase 1 PR5)
// loadDataFromServer — moved to DataIO.js.html (Phase 1 PR5)
// saveDataToServer — moved to DataIO.js.html (Phase 1 PR5)
// findNextUpcomingClasses — moved to DataCollection.js.html (Phase 1 PR3)
// --- UTILITY ---
// _getLocks — moved to LockManager.js.html (Phase 1 PR2)
// _saveLocks — moved to LockManager.js.html (Phase 1 PR2)
// acquireLock — moved to LockManager.js.html (Phase 1 PR2)
// releaseLock — moved to LockManager.js.html (Phase 1 PR2)
// releaseCurrentLock — moved to LockManager.js.html (Phase 1 PR2)
// refreshLockHeartbeat — moved to LockManager.js.html (Phase 1 PR2)
// _collectFromScheduleData, getAllTags, _collectFromAllCourses — moved to DataCollection.js.html (Phase 1 PR3)
// getGlobalAllTags, getGlobalAllCourseNames, getGlobalAllTeachers — moved to DataCollection.js.html (Phase 1 PR3)
// getShortUserName — moved to UtilityFunctions.js.html (Phase 1 PR1)
// isCurrentUserAdmin, canManageCurrentScheduleSettings — moved to ScheduleManager.js.html (Phase 1 PR6)
// ensureDataIds, buildCourseColorMap, sortClassrooms, checkTimeConflict — moved to DataCollection.js.html (Phase 1 PR3)
// generateUniqueId — moved to UtilityFunctions.js.html (Phase 1 PR1)
// stringToHashCode — moved to UtilityFunctions.js.html (Phase 1 PR1)
// timeToMinutes — moved to UtilityFunctions.js.html (Phase 1 PR1)
// formatTime — moved to UtilityFunctions.js.html (Phase 1 PR1)
// formatTimestampForFilename — moved to UtilityFunctions.js.html (Phase 1 PR1)
// _filterScheduleData, filterDataByTags, filterDataByActiveFilters — moved to FilterEngine.js.html (Phase 1 PR4)
// _forEachCourse, countOccurrences, updateAllOccurrences — moved to DataCollection.js.html (Phase 1 PR3)
// handleDrop — moved to ScheduleManager.js.html (Phase 1 PR6)
// hexToRgb — moved to UtilityFunctions.js.html (Phase 1 PR1)
// printScheduleToPdf — moved to PDFExport.js.html (Phase 1 PR7)
};
// --- Initialization ---
window.App = App; // Expose App to global scope for modules
// App.init() moved to Index.html — runs after all domain modules load (Phase 1 PR1)
})();
</script>