import { Database } from '../classes/database.js';
import { IDConverter } from '../classes/IDConverter.js';
import * as indexJs from './index.js';
/**
* Helper function to indicate what today's date is
* @param {string} root - the shadow root inside the monthly element to access the calendar button from
*/
export function indicateDate (root) {
const today = new Date();
if (currDate.getMonth() === today.getMonth()) {
root.querySelector('.monthly-calendar-button' + today.getDate()).style.color = 'red';
}
}
/**
* Helper function to handle turning current month RED when under the current year
* @param {HTMLElement} root - the shador root inside the yearly element to access the calendar button from
*/
export function indicateMonth (root) {
const today = new Date();
if (currDate.getYear() === today.getYear()) {
root.querySelector('.yearly-calendar-button' + today.getMonth()).style.color = 'red';
}
}
/*
* Workflow (to be implemented):
* When DOC loads, get id of current day and load in current day's dailyObj from database
* or create new blank dailyObj
* Make HTML element from dailyObj and displays it
* forward backward buttons:
* backward loads the latest available dailyObj entry;
* forward creates a blank entry with the correct date, or reads in if it already exists:
*/
// Define variables used throughout all code here
// ----------------------------------------------
// Elements and buttons found on all pages
let btnZoomOut, btnNextUnit, btnPrevUnit, btnNextEntry, btnPrevEntry;
// Vars used to setup entry indexing
let index, entries;
// currentDate based on entry, the actual currentDate will be generated whenever needed)
let currDate = new Date();
// Elements for the daily logs page
let dailyLog;
// Elements for the monthly logs page
let monthlyLog;
// Elements for the yearly logs page
let yearlyLog;
// -----------------------------------------------
// Labels object for mapping label names to colors
export const labels = {
finance: {
color: 'green'
},
fitness: {
color: 'darkviolet'
},
academics: {
color: 'grey'
},
household: {
color: 'orange'
},
work: {
color: 'red'
},
leisure: {
color: 'blue'
}
};
// End of variable definition
/**
* Wait for DOM to load before running script
* @param {setupScript} cb - Callback that uses DOM content to setup script
*/
document.addEventListener('DOMContentLoaded', setupScript);
/**
* Handles history navigation <p>
*
* Determines which log to load into view based off current view and calls the appropriate transition function
* (transitionDaily, transitionMonthly, transitionYearly.) <p>
*
* Will also check if history state stored a date, indicating movement to another Unit/Entry occurred. <p>
*
* If this is the case, the old date is loaded into currDate and the appropriate load function is called
* (loadDay, loadMonth, loadYear)
* @param {PopStateEvent} event - info on target page contained in state
*/
window.onpopstate = function (event) {
console.log('Current state.log: ' + event.state.view);
switch (event.state.view) {
case 'day':
transitionDaily();
currDate = event.state.date;
loadDay();
updateIndex();
break;
case 'month':
currDate = event.state.date;
loadMonth();
transitionMonthly();
break;
case 'year':
currDate = event.state.date;
loadYear();
transitionYearly();
break;
}
};
/**
* Helper called once DOM has loaded. <p>
*
* Will immediately push a state indicating current view is set to daily log to window history, <p>
*
* calls loadVars to load values into declared variables, <p>
*
* calls setupButtons to add onClickListeners to buttns
* @callback setupScript
*/
function setupScript () {
loadVars();
setupButtons();
// Fetches style from database and calls on helper to apply it
Database.fetch('S', (data) => {
if (data) {
indexJs.style.fontType = data.fontType;
indexJs.style.themeType = data.themeType;
indexJs.loadStyle();
} else {
console.log('No style was set yet!');
}
});
if (!history.state) {
window.history.replaceState({ view: 'day', date: currDate }, 'Daily Log', '#day');
} else {
currDate = history.state.date;
}
switch (history.state.view) {
case 'day':
loadDay();
transitionDaily();
break;
case 'month':
loadMonth();
transitionMonthly();
break;
case 'year':
loadYear();
transitionYearly();
break;
}
}
/**
* Values assigned to variables defined earlier <p>
*
*
* Classnames used to load divs for the 3 views, the associated icon group, and all bullet elements
* (currently sections holding list elements but will be replaced once custom bullet element is defined) <p>
*
* IDs used to load button elements
*/
function loadVars () {
dailyLog = document.getElementsByTagName('daily-log')[0];
monthlyLog = document.getElementsByTagName('monthly-log')[0];
yearlyLog = document.getElementsByTagName('yearly-log')[0];
btnZoomOut = document.getElementById('zoom-out-button');
btnNextUnit = document.getElementById('next-day');
btnPrevUnit = document.getElementById('prev-day');
btnNextEntry = document.getElementById('last-entry-forward');
btnPrevEntry = document.getElementById('last-entry-back');
Database.getEntryKeys((data) => {
entries = data;
updateIndex();
});
}
/**
* Appends a weather HTML div that shows the weather but only on the current day's daily log. <p>
*
* The weather is pulled from a third-party API based on the location of the client browser. The
* weather temperature starts off with fahrenheit units to begin with but can changed when the user
* clicks on it. <p>
*
* In the case of a fetch error or other error, it defaults to a question-mark style denoting unknown.
*/
function appendWeather () {
// check if we are looking at today's daily log (we don't want to display weather on other logs)
const todayDate = new Date();
if (currDate.getDate() === todayDate.getDate() &&
currDate.getMonth() === todayDate.getMonth() &&
currDate.getFullYear() === todayDate.getFullYear()) {
// create weather div container and append it to the daily log's header
const weatherDiv = document.createElement('div');
weatherDiv.className = 'container';
weatherDiv.innerHTML = `
<div class="weather-container">
<div class="weather-icon">
<img src="../assets/icons/unknown.png" alt="">
</div>
<div class="temperature-value">
<p>  °<span>F</span></p>
</div>
<div class="temperature-description">
<p>-</p>
</div>
</div>
`;
const temp = document.querySelector('daily-log').shadowRoot.querySelector('.header').querySelector('h1');
temp.style = 'margin-left: 0';
dailyLog.shadowRoot.querySelector('#daily-header').insertBefore(weatherDiv, temp);
// Select Elements
const iconElement = weatherDiv.querySelector('.weather-icon');
const tempElement = weatherDiv.querySelector('.temperature-value p');
const descElement = weatherDiv.querySelector('.temperature-description p');
// const locationElement = this.shadowRoot.querySelector(".location p");
// const notificationElement = this.shadowRoot.querySelector(".notification");
// Data
const weather = {};
weather.temperature = {
unit: 'celsius'
};
// Constants and variables
const KELVIN = 273;
// API key
const key = '3f70f77aa960728d939b3bee01d7bbda';
// Check if browser supports geolocation
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(setPosition, showError);
} else {
console.error("Browser Doesn't Support Geolocation");
}
// Set user's position
function setPosition (position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
getWeather(latitude, longitude);
}
// Show error when there is an issue with geolocation service
function showError (error) {
console.error(error.message);
}
// Get weather from API provider
function getWeather (latitude, longitude) {
const api = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${key}`;
console.log(api); // FIXME
fetch(api)
.then(function (response) {
const data = response.json();
return data;
})
.then(function (data) {
weather.temperature.value = Math.floor(data.main.temp - KELVIN);
weather.description = data.weather[0].description;
weather.iconId = data.weather[0].icon;
weather.city = data.name;
weather.country = data.sys.country;
})
.then(function () {
displayWeather();
});
}
// Display weather to UI
function displayWeather () {
if (document.getElementsByTagName('html')[0].className !== '') {
iconElement.innerHTML = `<img src="../assets/icons/${weather.iconId}_d.png"/>`;
} else {
iconElement.innerHTML = `<img src="../assets/icons/${weather.iconId}.png"/>`;
}
tempElement.innerHTML = `${Math.floor(celsiusToFahrenheit(weather.temperature.value))}°<span>F</span>`;
descElement.innerHTML = weather.description;
weather.temperature.unit = 'fahrenheit';
// locationElement.innerHTML = `${weather.city}, ${weather.country}`;
}
// C to F conversion
function celsiusToFahrenheit (temperature) {
return (temperature * 9 / 5) + 32;
}
// When the user clicks on the temperature element
tempElement.addEventListener('click', function () {
if (weather.temperature.value === undefined) return;
if (weather.temperature.unit === 'fahrenheit') {
tempElement.innerHTML = `${weather.temperature.value}°<span>C</span>`;
weather.temperature.unit = 'celsius';
} else {
let fahrenheit = celsiusToFahrenheit(weather.temperature.value);
fahrenheit = Math.floor(fahrenheit);
tempElement.innerHTML = `${fahrenheit}°<span>F</span>`;
weather.temperature.unit = 'fahrenheit';
}
});
} else {
document.querySelector('daily-log').shadowRoot.querySelector('.header').querySelector('h1').style = 'margin-left: 13vw';
}
}
/**
* Functionality applied via onClickListeners to the following buttons: <p>
*
* Zoom Out button - using zoomOut <p>
*
* Bullet items - using editBullet <p>
*
* Add bullet buttons (that are dynamically added to each section) - using addBullet <p>
*
* Create section button - using createSection <p>
*
* Minimize section button - using jQuery's slideToggle function
*/
function setupButtons () {
btnZoomOut.addEventListener('click', zoomOut);
btnNextUnit.addEventListener('click', () => { navigateUnit(1); });
btnPrevUnit.addEventListener('click', () => { navigateUnit(-1); });
btnNextEntry.addEventListener('click', () => { navigateEntry(1); });
btnPrevEntry.addEventListener('click', () => { navigateEntry(-1); });
}
/**
* Zooms out to appropriate view based off history state <p>
*
* Calls finalizeInputs to finish any bullets that are being editted <p>
*
* Checks the current history state's view to determine which transition to make <p>
*
* Pushes the current view to history state and calls appropriate transition function
* (transitionMonthly, transitionDaily)
*/
function zoomOut () {
// console.log('You clicked on the zoom out button');
let zoomOutDaily;
let zoomOutMonthly;
switch (history.state.view) {
case 'day':
zoomOutDaily = document.querySelector('#zoom-out-transition');
// timing for the daily to monthly animation
zoomOutDaily.classList.add('zoomOutDaily');
setTimeout(function () {
zoomOutDaily.classList.remove('zoomOutDaily');
}, 900);
// pushing daily to monthly
setTimeout(function () {
window.history.pushState({ view: 'month', date: currDate }, 'Monthly Log', '#month');
loadMonth();
transitionMonthly();
}, 140);
break;
case 'month':
zoomOutMonthly = document.querySelector('#zoom-out-transition-2');
// timing for the monthly to yearly animation
zoomOutMonthly.classList.add('zoomOutMonthly');
setTimeout(function () {
zoomOutMonthly.classList.remove('zoomOutMonthly');
}, 900);
// pushing monthly to history
setTimeout(function () {
window.history.pushState({ view: 'year', date: currDate }, 'Yearly Log', '#year');
loadYear();
transitionYearly();
}, 140);
break;
}
}
/**
* Handles transitioning from Monthly view to Daily view <p>
*
* Daily div is set to visible, Monthly div is set to invisible <p>
*
* Icon groups for entry jumping are made available to Daily view
*/
function transitionDaily () {
dailyLog.style.display = 'block';
monthlyLog.style.display = 'none';
yearlyLog.style.display = 'none';
btnPrevEntry.disabled = 0;
btnNextEntry.disabled = 0;
}
/**
* Handles transitioning from either Daily or Yearly view to Monthly view <p>
*
* Daily and Yearly div is set to invisible, Monthly div is set to visible <p>
*
* Icon groups for entry jumping are made unavailable to Monthly and Yearly view <p>
*
* Zoom out icon is enabled in Monthly and Daily view
*/
function transitionMonthly () {
dailyLog.style.display = 'none';
monthlyLog.style.display = 'block';
yearlyLog.style.display = 'none';
// divEntryNav.style.display = 'none';
btnZoomOut.disabled = 0;
btnZoomOut.style.cursor = 'pointer';
btnPrevEntry.disabled = 1;
btnNextEntry.disabled = 1;
btnZoomOut.style.backgroundColor = '';
}
/**
* Handles transitioning from Monthly view to Yearly view <p>
*
* Yearly div is set to visible, Monthly div is set to invisible <p>
*
* Zoom out icon is disabled in Yearly View
*/
function transitionYearly () {
dailyLog.style.display = 'none';
monthlyLog.style.display = 'none';
yearlyLog.style.display = 'block';
btnZoomOut.disabled = 1;
btnZoomOut.style.cursor = 'default';
btnPrevEntry.disabled = 1;
btnNextEntry.disabled = 1;
btnZoomOut.style.backgroundColor = 'transparent';
}
// New & unprocessed code -----------------------------------------------------------------------
/**
* Loads the current day into display <p>
*
* Calls generateID, attempts to use ID to retrieve data for the current day <p>
*
* If data is returned, calls on day custom element's setter for data <p>
*
* Otherwise, either an error occurred or ID isn't registered <p>
*
* Regardless, day custom element's blank setter is called <p>
*
* In both cases, the day element is appended to appropriate location in document (TODO)
*
* @param {string} ID - Day ID used to load current date, defaults to generation if not provided
*/
function loadDay (ID = IDConverter.generateID('day', currDate)) {
const dayElem = document.createElement('daily-log');
Database.fetch(ID, (data) => {
if (data) {
dayElem.data = [ID, data, updateEntries];
} else {
console.log('Creating a new template daily-log element');
dayElem.data = [ID, {}, updateEntries];
}
});
// append dayElem to internal content
dailyLog.shadowRoot.querySelector('div.daily').style.display = 'block';
document.getElementById('internal-content').replaceChild(dayElem, dailyLog);
dailyLog = dayElem;
dailyLog.style.display = 'block';
appendWeather();
}
/**
* Description here
*
*/
function loadMonth (ID = IDConverter.generateID('month', currDate)) {
const monthElem = document.createElement('monthly-log');
Database.fetch(ID, (data) => {
if (data) {
monthElem.data = [ID, data, zoomIn];
} else {
console.log('Creating a new template monthly-log element');
monthElem.data = [ID, {}, zoomIn];
}
});
// apend monthElem to internal content
monthElem.shadowRoot.querySelector('div.monthly').style.display = 'block';
document.getElementById('internal-content').replaceChild(monthElem, monthlyLog);
monthlyLog = monthElem;
monthlyLog.style.display = 'block';
}
/**
* Description here
*
*/
function loadYear (ID = IDConverter.generateID('year', currDate)) {
const yearElem = document.createElement('yearly-log');
Database.fetch(ID, (data) => {
if (data) {
yearElem.data = [ID, data, zoomIn];
} else {
console.log('Creationg a new template yearly-log element');
yearElem.data = [ID, {}, zoomIn];
}
});
// append yearElem to internal content
yearElem.shadowRoot.querySelector('div.yearly').style.display = 'grid';
document.getElementById('internal-content').replaceChild(yearElem, yearlyLog);
yearlyLog = yearElem;
yearlyLog.style.display = 'block';
}
/**
* Handles the actual navigation of units by incre/decrements of one <p>
*
* Used to provide functionality to next/prev Unit buttons <p>
*
* Updates currentDate, records date and view to history state, then loads the next Entry
* (loadDay, loadMonth, loadYear) <p>
*
* Performs a toggleCheck afterwards to update index and entries (dayID[]) as well as
* propertly disable prev/next buttons if navigation results in indexing to extremes (0 or length-1)
*
* @param {Number} amount - of Units to move (+/- 1)
*/
function navigateUnit (amount) {
switch (history.state.view) {
case 'day':
currDate.setDate(currDate.getDate() + amount);
window.history.pushState({ view: 'day', date: currDate }, 'Daily Log', '#day');
loadDay();
updateIndex();
break;
case 'month':
currDate.setMonth(currDate.getMonth() + amount, 1);
window.history.pushState({ view: 'month', date: currDate }, 'Monthly Log', '#month');
loadMonth();
break;
case 'year':
currDate.setFullYear(currDate.getFullYear() + amount);
window.history.pushState({ view: 'year', date: currDate }, 'Yearly Log', '#year');
loadYear();
break;
}
}
/**
* Handles the navigation of entries by incre/decrements of one <p>
*
* Used to provide functionality to next/prev Entry buttons (only in daily view) <p>
*
* Relies on Unit movement being correctly disabled by the 'toggleCheck' function
* to avoid moving beyond the bounds of entries (dayID[])
* and to only allow Entry movement under the daily view <p>
*
* Uses index after navigation to load target Day's ID and set's current Date with an ID conversion <p>
*
* Records date and view to history state, then loads the next/prev Entry using the dayID retrieved from entries (dayID[]) <p>
*
* Performs a toggleCheck afterwards to update index and entries (dayID[]) as well as
* propertly disable prev/next buttons if navigation results in indexing to extremes (0 or length-1)
*
* @param {Number} amount - of Entries to move (+/- 1)
*/
function navigateEntry (amount) {
/* Generate the index to navigate to by either incrementing of decrementing by 1
* - handle the case of traversing from right before end entry (index would be ON the last entry and attempt to move right)
*/
index = index + amount;
if (index >= entries.length) {
// avoid index out of bounds retrieval attempts and crashes, do the checks against end first
if (IDConverter.generateID('day', currDate) < entries[index - 1]) {
index = entries.length - 1;
}
}
const targetID = entries[index];
currDate = IDConverter.getDateFromID(targetID, 'day');
window.history.pushState({ view: 'day', date: currDate }, 'Daily Log', '#day');
loadDay(targetID);
updateIndex(targetID);
}
/* For quick commenting out of code */
/**
* Performs updates to index and entries, then decides which buttons need to be disabled <p>
*
* A lookup is done on today's ID to determine index (using a binary search on entries since it's a sorted array),
* if the day hasn't been recorded yet, the day object will handle deciding whether or not to add the day to entries <p>
*
* Calls function to toggle buttons based off updated Index and whether or not currentID is 'inBounds'
* @param {string} [currID = IDConverter.generateID('day', currDate)] - current Day's ID used to update indexing and decide whether to toggle buttons
*/
function updateIndex (currID = IDConverter.generateID('day', currDate)) {
// Generate the ID and determine index
index = IDConverter.generateIndex(entries, currID);
if (index === 0 && currID < entries[index]) {
index -= 1;
}
// Boundedness is being between the start (index === 0) and end (entries >= entries[index])
toggleCheck(index > 0 && currID < entries[index]);
}
/**
* Handles additions to entries (dayID[]), which can occur in 2 ways: <p>
*
* - Insertion at end of array, which requires expansion of the array (checked first) <p>
*
* - Taking up an existing index and 'pushing' all other entries afterwards (using splice) <p>
*
* Otherwise, logs an error indicating function was called unnecsarilly
*/
export function updateEntries (currID = IDConverter.generateID('day', currDate), targetIndex) {
if (targetIndex) {
index = targetIndex;
} else {
index = IDConverter.generateIndex(entries, currID);
}
if (index === entries.length) {
entries.push(currID);
} else if (entries[index] !== currID) {
entries.splice(index, 0, currID);
} else {
console.log(`updateEntries has already been called for ID ${currID} at index ${index}`);
}
}
/* For quick commenting out of code */
/**
* Toggles relavant Entry navigation buttons based on: <p>
*
* - Whether or not currently in daily view <p>
*
* - Whether or not index navigation in entries (dayID[]) would result in out of bounds
*
* @param {bool} inBounds - whether or not the current day is within the list
* (to avoid disabling >> for days right before the last entry)
*/
function toggleCheck (inBounds = false) {
// Either beginning or end of list indicates respective prev/next Entry toggling should be disabled
if ((index <= 0 && !inBounds) || history.state.view !== 'day') {
btnPrevEntry.disabled = true;
btnPrevEntry.style.cursor = 'default';
btnPrevEntry.style.backgroundColor = 'transparent';
} else {
btnPrevEntry.disabled = false;
btnPrevEntry.style.cursor = 'pointer';
btnPrevEntry.style.backgroundColor = '';
}
if ((index >= entries.length - 1 && !inBounds) || history.state.view !== 'day') {
btnNextEntry.disabled = true;
btnNextEntry.style.cursor = 'default';
btnNextEntry.style.background = 'transparent';
} else {
btnNextEntry.disabled = false;
btnNextEntry.style.cursor = 'pointer';
btnNextEntry.style.backgroundColor = '';
}
}
/* For quick commenting out of code */
/**
* Provides functionality to calendar view in yearly/monthly logs <p>
*
* Access to contents (and therefor target date via ID) in the element that was triggered through event param <p>
*
* Converts stored ID to date with IDConverter and loads into currDate <p>
*
* Reads history state's current view to decide whether to transition to Monthly or Daily view,
* then calls appropriate transition + load function (loadDay, loadMonth)
*
* @param {OnClickEvent} event - that triggered this function, provides access to target ID
*/
export function zoomIn (event) {
let zoomOutDailyRev;
let zoomOutMonthlyRev;
switch (history.state.view) {
case 'month':
currDate = IDConverter.getDateFromID(event.target.id, 'day');
window.history.pushState({ view: 'day', date: currDate }, 'Daily Log', '#day');
// timing for the daily to monthly animation
zoomOutDailyRev = document.querySelector('#zoom-out-transition');
zoomOutDailyRev.classList.add('zoomOutDailyRev');
setTimeout(function () {
zoomOutDailyRev.classList.remove('zoomOutDailyRev');
}, 700);
// pushing daily to monthly
setTimeout(function () {
loadDay();
transitionDaily();
updateIndex();
}, 520);
break;
case 'year':
currDate = IDConverter.getDateFromID(event.target.id, 'month');
window.history.pushState({ view: 'month', date: currDate }, 'Monthly Log', '#month');
// timing for the daily to monthly animation
zoomOutMonthlyRev = document.querySelector('#zoom-out-transition-2');
zoomOutMonthlyRev.classList.add('zoomOutMonthlyRev');
setTimeout(function () {
zoomOutMonthlyRev.classList.remove('zoomOutMonthlyRev');
}, 700);
// pushing daily to monthly
setTimeout(function () {
loadMonth();
transitionMonthly();
}, 520);
break;
}
}
/* For quick commenting out of code */
// Notes and old code -------------------------------------------------------------------------------
/*
$(document).click(function(event) {
var $target = $(event.target);
if(!$target.closest('#menucontainer').length &&
$('#menucontainer').is(":visible")) {
$('#menucontainer').hide();
console.log('This is a build test');
}
});
*/