diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a3fe4a2..4cddf05e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,82 @@
# Change Log
+## v1.2.0 - Time, Other Modules, Seasons, Moons and Notes
+
+This update was a long time coming so apologies for making you wait for it. This update with most of the currently requested changes to Simple Calendar
+
+### Time
+
+Simple Calendar now supports time of day! What this means is that Simple Calendar can now be tied into FoundryVTTs game world time. This allows any other module that ties into the game world time to be updated whe Simple Calendar updates or update Simple Calendar when they change the game world time.
+
+There are 4 different options on how to tie into the game world time to achieve the exact level of interaction you want for your world. They are:
+
+Option|Description|Update Game World Time|When Game World Time is Updated
+--------|--------------------|-------------------------------------------------|----------------------------------------------------------
+None (default)|Simple Calendar does not interact with the game world time at all. This setting is ideal if you want to keep Simple Calendar isolated from other modules.|Does not update the game world time|Simple Calendar is not updated when the game world time is updated by something else.
+Self|Treats Simple Calendar as the authority source for the game world time. This setting is ideal when you want Simple Calendar to be in control of the games time and don't want other modules updating Simple Calendar|Updates the game world time to match what is in Simple Calendar.|Combat round changes will update Simple Calendars time. Simple Calendar will ignore updates from all others modules.
+Third Party Module|This will instruct Simple Calendar to just display the Time in the game world time. All date changing controls are disabled and the changing of time relies 100% on another module. This setting is ideal if you are just want to use Simple Calenar to display the date in calendar form and/or take advantage of the notes.|Does not update the game world time.|Updates it's display everytime the game world time is changed, following what the other modules say the time is.
+Mixed|This option is a blend of the self and third party options. Simple calendar can change the game world time and and changes made by other modules are reflected in Simple Calendar. This setting is ideal if you want to use Simple Calendar and another module to change the game time.|Will update the game world time|Will update it's own time based on changes to the game world time, following what other modules say the time is.
+
+You can check out the [configuration](./docs/Configuration.md#game-world-time-integration) section for more details.
+
+#### Simple Calendar Clock
+
+There is now a time of day clock that displays below the calendar to show the current time of the current day.
+The GM can manually control this clock if they wish, or they can start the clock and have it update as real time passes.
+The clock does also update as combat rounds pass. For more details on the clock check out [here](./docs/UsingTheCalendar.md#simple-calendars-clock).
+
+#### Configuration
+
+You can configure the calendar with how many hours in a day, minutes in an hour and seconds in a minute.
+
+You can also set the ratio of game time to real world seconds. This is used when the built-in clock is running and needs to update the game time as real world seconds pass. This ratio can be a decimal.
+A ratio of 1 would mean for every second that passes in the real world 1 second passes in game time, a ratio of 2 would mean for every second of real world time 2 seconds pass in game.
+
+### Other Modules
+
+Simple Calendar also now interfaces with other modules. These modules can be used to adjust the game world time and have those changes reflected in Simple Calendar.
+
+For two of the more common modules Simple Calendar can also import their settings into Simple Calendar or export Simple Calendars settings into those modules. The two modules that support this are about-time and Calendar/Weather. Check out this [documentation](./docs/Configuration.md#third-party-module-importexport) for more details on how it all works.
+
+
+### Seasons
+
+Simple Calendar now supports seasons. Any number of seasons can be added to a calendar, and you are able to specify the following options for each season:
+
+Setting | Description
+--------|------------
+Season Name | Specify a custom name of the season.
+Starting Month | From a drop down choose which month this season starts in. This drop down is populated based on the custom months that have been set up.
+Starting Day | From a drop down choose which day of the the starting month this season begins on. This drop down is populated with a list of days based on the staring month selected.
+Color | Seasons can be assigned a color, this color is used as the background color for the calendar display when it is the current season. There is a list of predefined colors that work well for standard season and the option to enter a custom color.
+Custom Color | If the color option is set to Custom Color an option will appear where you can enter a custom Hex representation of a color to use for the seasons color.
+
+The calendar display has also been updated so that right below the month and year the name of the current season will be displayed.
+
+The background color of the calendar is also set based on the current season and its color settings.
+
+I think this gives the best approach for defining seasons and allowing customization in how they look.
+
+### Moons
+
+Simple Calendar now supports the addition of moons. Any number of moons can be added to a calendar, and they can be customized to meet your needs.
+For details on how to add and customize a moon please check out the [configuration documentation](./docs/Configuration.md#moon-settings).
+
+The calendar now also displays the important (single day) moon phases on the calendar as well as the moon phase for the current day and selected day.
+
+The predefined calendars have been updated to set up their moon(s) for the calendar.
+
+### Notes
+
+A configuration option has been added to allow players to add their own notes to the Calendar. If enabled they will see the "Add New Note" button on the main calendar display.
+
+**Important**: For a user to add their own note a GM must also be logged in at the same time, a warning is displayed if a user attempts to add a note when not GM is logged in.
+
+### Documentation
+
+I did a complete re-organization/clean up of all the documentation around Simple Calendar. I also added in links within the Simple Calendar configuration window to this documentation. I hope this will help make configuration and use of the tool easier for all.
+
+
## v1.1.8 - Bug Fixes
- Fixed a rare bug where the day of the week a month starts on would be incorrect.
diff --git a/README.md b/README.md
index 878d6a52..61fe8a4d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,12 @@
-[![foundry](https://img.shields.io/badge/Foundry-0.7.9-orange)](https://foundryvtt.com/releases/)
+[![Foundry Core Compatible Version](https://img.shields.io/badge/dynamic/json.svg?url=https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/master/src/module.json&label=Foundry&query=$.compatibleCoreVersion&colorB=orange)](https://foundryvtt.com/releases/)
![GitHub package.json version](https://img.shields.io/github/package-json/v/vigoren/foundryvtt-simple-calendar)
[![license](https://img.shields.io/badge/license-MIT-blue)](https://github.com/vigoren/foundryvtt-simple-calendar/blob/main/LICENSE)
-[![jest](https://jestjs.io/img/jest-badge.svg)](https://github.com/facebook/jest)
+![GitHub Workflow Status](https://img.shields.io/github/workflow/status/vigoren/foundryvtt-simple-calendar/Node.js%20CI)
[![codecov](https://codecov.io/gh/vigoren/foundryvtt-simple-calendar/branch/main/graph/badge.svg?token=43TJ117WP1)](https://codecov.io/gh/vigoren/foundryvtt-simple-calendar)
+[![GitHub release (latest by date)](https://img.shields.io/github/downloads/vigoren/foundryvtt-simple-calendar/latest/total)](https://github.com/vigoren/foundryvtt-simple-calendar/releases/latest)
+[![forge](https://img.shields.io/badge/dynamic/json?label=Forge%20Installs&query=package.installs&suffix=%&url=https://forge-vtt.com/api/bazaar/package/foundryvtt-simple-calendar&colorB=3d8b41)](https://forge-vtt.com/bazaar#package=foundryvtt-simple-calendar)
+[![Foundry Hub Endorsements](https://img.shields.io/endpoint?logoColor=white&url=https://www.foundryvtt-hub.com/wp-json/hubapi/v1/package/foundryvtt-simple-calendar/shield/endorsements)](https://www.foundryvtt-hub.com/package/foundryvtt-simple-calendar/)
+[![Foundry Hub Comments](https://img.shields.io/endpoint?logoColor=white&url=https://www.foundryvtt-hub.com/wp-json/hubapi/v1/package/foundryvtt-simple-calendar/shield/comments)](https://www.foundryvtt-hub.com/package/foundryvtt-simple-calendar/)
![Logo](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/logo.png)
@@ -10,29 +14,43 @@
A simple calendar module for [FoundryVTT](https://foundryvtt.com/) that is system independent.
-This module allows you to create a calendar with any number of months per year and any number of days per month for your game world.
+This module allows you to create a calendar with any number of months per year, any number of days per month and customizable hours, minutes and seconds for your game world.
It is intended as a way for a GM to show a calendar like interface that maps to their in game world.
-The simple calendar module does not keep track of time or tie into any system to advance the day, date changing is a manual process by the GM.
-If you are looking for a module that tracks in game time and has weather related effects I recommend you check out the [Calendar/Weather module](https://www.foundryvtt-hub.com/package/calendar-weather/).
-
-## GM Features:
-
-* Configure the calendar to meet your worlds needs
- * Set the year as well as add any prefix or postfix to the years name
- * Define how many months in a year
- * Set a custom name for each month
- * Set the number of days in each month
- * Choose if months are considered intercalary
- * Set up your own Leap Year rules
-* Set and change the current day as your game story progresses
-* Add notes to specific days on the calendar to remind yourself of events or other world related things
- * These notes can either be visible to players as well as the GM or just the GM
-
-## Player Features:
-
-* Browse a calendar interface to see the years, months and day of the game world
-* Select days to see any notes/events specific to that day
+## Features
+ Simple Calendar has a number of features that make it a great time keeping tool for your games!
+
+### For GMs
+* Complete customization of the calendar to meet your worlds needs:
+ * Set the year as well as add any prefix or postfix to the years name.
+ * Define how many months in a year.
+ * Set a custom name, the number of days and days during a leap year (if applicable) for each month.
+ * Choose if months are considered intercalary (fall outside normal months).
+ * Set the number hours in a day, minutes in an hour and seconds in a minute.
+ * Set up your own Leap Year rules.
+ * Set up different seasons for your calendar and how they are displayed to the users.
+ * Set up your own custom moons.
+ * Or choose from a selection of [preset calendars](./docs/Configuration.md#predefined-calendars).
+* Set and change the current day and time as your game story progresses or have it automatically advance based on real world time and passing combat rounds.
+* Add notes to specific days on the calendar to remind yourself of events or other world related things.
+ * These notes can either be visible to players as well as the GM or just the GM.
+
+### For Players
+
+* Browse a calendar interface to see the years, months and day of the game world.
+* See the current day and time of the game world.
+* Select days to view any notes/events specific to that day.
+* If the GM allows it, the ability to add their own notes to the calendar.
+
+## Contents
+
+- [Installation](#installing-the-module)
+- [Compatible Modules](#compatible-modules)
+- [Accessing and using the calendar](./docs/UsingTheCalendar.md)
+- [Changing the date and time](./docs/UpdatingDateTime.md)
+- [Notes](./docs/Notes.md)
+- [Configuring your Calendar](./docs/Configuration.md)
+- [Macros](./docs/Macros.md)
## Installing The Module
@@ -47,176 +65,15 @@ To install using the module json file, use this link [https://github.com/vigoren
To install the most recent version of the module, view the releases section to the right of the main GitHub page.
Selecting the latest release will bring you to a page where you can download the module.zip asset. This will contain everything you need to manually install the module.
-## Accessing and using the Calendar
-The module adds a calendar button to the basic controls section of the layer controls. Clicking on this will open the module window
-
-![Calendar Button Location](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/layers-button.png)
-
-The above image helps shows the controls, they are detailed out below.
-
-Control | Description
-------- | -----------
-Previous/Next | Allow the user to change which month/year they are currently viewing.
-Today Button | Changes the calendar so that the current day (in the game world) is visible and selected.
-Blue Circle Day | This indicates the current day in the game world, can be changed by the GM
-Green Circle Day | This indicates the day the user currently has selected. This will show any notes on this day.
-Red Indicator | This shows on any days that have notes that the user can see. It will show the number of notes on that day up to 99.
-Notes List | Any notes that appear in this list can be clicked on to open the note details.
-
-### Note Details
-
-The note details shows all the information about a specific note: the date the note is for, the title of the note and the content of the note.
-
-![Calendar Button Location](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/note-view.png)
-
-## Updating the Current Date
-The GM version of the module looks a little different, with the addition of controls to change the current date and a button to enter the configuration.
-
-![GM View](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/gm-view.png)
-
-The above image helps shows the controls, they are detailed out below.
-
-Control | Description
-------- | -----------
-Day Back/Forward | This moves the current day forward or back one day.
-Month Back/Forward | This moves the current month forward or back one month. The current day will be mapped to the same day as the old month, or the last day of the month if the old month has more days.
-Year Back/Forward | This moves the current year forward or back one year. The current month and day will stay the same in the new year.
-Apply | This will apply the changes, saving the new current day in the settings and updating all of the players calendars to reflect the new current day.
-Configuration | This opens up the configuration dialog to allow the GM to fully customize the calendar.
-Add New Note Button | This will open the add notes dialog to add a new note for the selected day.
-
-## Adding, Editing and Removing notes
-
-The GM has the ability to add new notes by clicking on the add new note button for a selected date. This will open a dialog where the details of the note can be filled out.
-
-![Calendar Button Location](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/note-new.png)
-
-Field | Description
-------- | -----------
-Note Title | The title for the note, this title will appear in the listing of notes for the day.
-Player Visible | If this note can be seen by players or if it is for the GM only.
-Note Repeats | If this note repeats weekly, monthly, yearly or never. If the note repeats monthly and it is on a day that some months don't have (eg the 31st) months that don't go to that day will not have this note.
-Details | Here you can enter the details of a note using the built in text editor.
-
-After all the details are filled out you can save the note.
-
-**Important**: If you have not saved the content in the text editor using the text editor save button, a warning will appear when you try to save the note letting you know.
-
-The GM can also edit or delete existing notes. To do this click on an existing note, at the bottom of the note view 2 additional buttons will be visible, Edit and Delete.
-
-Button | Description
-------- | -----------
-The Edit button | This will load the contents of the note in the same editor as creating a new note.
-The Delete button | This will open up a confirmation dialog, where selecting delete again will permanently remove the note.
-
-## Configuring Your calendar
-
-Configuration of the calendar is straight forward. As the GM open the configuration dialog and start customizing your calendar!
-
-### General Settings
-
-This tab allows you to set some general settings for the entire calendar.
-
-#### Predefined Calendars
-
-This setting lets you choose from a list of predefined calendars to get your calendar started with. The following calendars can be selected to configure the game calendar:
-
-Calendar|Description|Initial Date
---------|-----------|-------------
-Gregorian|This the standard real life calendar|The current date will be used
-Eberron| This is the calendar from the Eberron setting for Dungeons and Dragons | Zarantyr 1, 998 YK
-Exandrian |This is the calendar from the Exandria setting for Dungeons and Dragons | Horisal 1, 812 P.D.
-Golarian | This is the calendar from the Pathfinder game | Abadius 1, 4710 AR
-Greyhawk | This is the calendar from the Greyhawk setting for Dungeons and Dragons | Needfest 1, 591 cy
-Harptos | This is the calendar used across Faerun in the Forgotten Realms | Hammer 1, 1495 DR
-Warhammer | This is the calendar used by the Imperium in the Fantasy Warhammer game | Hexenstag 1, 2522
-
-All of these calendars can be further customized after they are loaded. They are here to provide a simple starting point for your game.
-
-#### Other Settings
-
-These are the other settings availible under the General Settings tab
-
-Setting | Description
--------- | ----------
-Note Default Player Visibility | For new notes, if by default the player visibility option is checked or not.
-
-
-### Year Settings
-
-This tab allows you to change some settings about the years in your game world
-
-Setting | Description
--------- | ----------
-Current Year | The Current year your game world is in. This can be any positive number.
-Year Prefix | Text that will appear before the year number.
-Year Postfix | Text that will appear after the year number.
-
-### Month Settings
-
-This tab displays all the months that exist in the calendar. Here you can change month names, the number of days in a month, remove a month, add a new month or remove all months.
-
-Setting | Description
--------- | ----------
-Month Name | These text boxes for each month allow you to change the name of an existing month.
-Number of Days | These text boxes for each month allow you to change the number of days in each month. A month can have a minimum of 0 days.
-Intercalary Month | An intercalary month is one that does not follow the standard month numbering and is skipped.
Example: If we were to add an intercalary month between January and February, January would still be considered the first month and February would be considered the second month. The new month does not get a number.
Intercalary months also do not count towards the years total days nor do they affect the day of the week subsequent months start on.
-Include Intercalary Month In Total Day Count | When you select a month to be intercalary, an option will show to include these days as part of the years total days and have its days afect the day of the week subsequent months start on. The month though still is not numbered.
-Remove Button | These buttons for each month allow you to remove the month from the list.
-Add New Month Button | This button will add a new month to the bottom of the list with a default name and number of days that you can then configure to your liking.
-Remove All Months Button | This button will remove all of the months from the list.
-
-
-### Weekday Settings
-
-This section displays all the weekdays that exist in the calendar. Weekdays are used to determine how wide the calendar display should be and how to make month days to each day of the week.
-
-Setting | Description
--------- | ----------
-Weekday Name | These text boxes for each weekday allow you to change the name of an existing weekday.
-Remove Button | These buttons for each weekday allow you to remove the weekday from the list.
-Add New Weekday Button | This button will add a new weekday to the bottom of the list with a default name that you can then configure to your liking.
-Remove All Weekdays Button | This button will remove all of the weekdays from the list.
-
-### Leap Year Settings
-
-This section allows the GM to configure how leap years work for this calendar.
-
-Setting | Description
--------- | ----------
-Leap Year Rule | Which ruleset to follow when determining leap years. The options are
- None: The calendar contains no leap years
- Gregorian: The calendars leap year rules are like the standard calendar (Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these years are leap years if they are exactly divisible by 400)
- Custom: Allows you to specify n interval in years for when a leap year happens.
-When Leap Years Happen | **This only appears if the Custom leap year rule is selected**.
The number of years when a leap year occurs. Example a value of 5 would mean every 5th year is a leap year.
-Months List | **This only appears if the Custom or Gregorian leap year rule is selected**.
A list of months will appear that shows each month, and a textbox where you can change the number of days the corresponding month has during a leap year. A month can have a minimum of 0 leap year days.
-
-After you have changed the settings to your liking don't forget to save the configuration by hitting the Save Configuration button!
-
-## Macro's
-
-You can create macros that will open up the Simple Calendar interface when used. To start create a new script macro and enter this as the command:
-
-```javascript
-SimpleCalendar.show();
-```
-
-**Important**: If this macro is intended to be useable by players don't forget to configure the Macros permissions for all players. It will need to be set to at least the "Limited", permission level.
+## Compatible Modules
+These are other time keeping modules that Simple Calendar can work if they are installed in your world.
-The show function can take 3 parameters to set the year, month and day that the calendar opens up to.
+**Important**: None of these modules are required, the option to work with them is available to make a GMs life easier if they want to use Simple Calendar but have another of these modules installed.
-Parameter|Type|Default|Details
----------|----|-------|-------
-Year | number or null | null | The year to open the calendar too. If null is passed in it will open the calendar to the year the user last viewed
-Month | number or null | null | The month to open the calendar too.
The month is expected to start at 0, or be the index of the month to show. This way intercalary months can be easily chosen by using their index as they don't have a month number.
-1 can be passed in to view the last month of the year.
If null is passed in the calendar will open to the month the user last viewed.
-Day | number or null | null | The day of the month to select.
The day is expected to start at 1.
-1 can be passed in to select the last day of the month.
If null is passed in the selected day will be the last day the user selected, if any.
+- [about-time](https://foundryvtt.com/packages/about-time): See the [about-time module configuration for Simple Calendar](./docs/Configuration.md#about-time) for more information.
+- [Calendar/Weather](https://foundryvtt.com/packages/calendar-weather): See the [Calendar/Weather module configuration for Simple Calendar](./docs/Configuration.md#calendarweather) for more information.
-### Examples
-All these examples assume we are using a standard Gregorian calendar.
-Open the calendar to August 2003
-```javascript
-SimpleCalendar.show(2003, 7);
-```
+## Credits
-Open the calendar to December 1999 and select the 25th day
-```javascript
-SimpleCalendar.show(1999, 11, 25);
-```
+Moon Icons by [Wolf Böse](https://thenounproject.com/neuedeutsche/)
diff --git a/__mocks__/form-application.ts b/__mocks__/form-application.ts
index 0520646c..2f0d2059 100644
--- a/__mocks__/form-application.ts
+++ b/__mocks__/form-application.ts
@@ -41,17 +41,28 @@ class FormApplication extends Application{
weekdayNames.value = 'Z';
+ const seasonNames = document.createElement('input');
+ seasonNames.setAttribute('data-index', '0');
+ seasonNames.value = 'Spring';
+ const seasonStart = document.createElement('input');
+ seasonStart.setAttribute('data-index', '0');
+ seasonStart.value = '2';
+ const seasonColor = document.createElement('input');
+ seasonColor.setAttribute('data-index', '0');
+ seasonColor.value = '#ffffff';
+
+
this.element = {
find: jest.fn()
- .mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return '';})};})})
- .mockReturnValueOnce([monthNames]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})})
- .mockReturnValueOnce([monthNames]).mockReturnValueOnce([sameMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'custom';})};})})
- .mockReturnValueOnce([monthNames]).mockReturnValueOnce([sameMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'custom';})};})})
- .mockReturnValueOnce([monthNames]).mockReturnValueOnce([monthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})})
- .mockReturnValueOnce([monthNames]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})})
- .mockReturnValueOnce([monthNames]).mockReturnValueOnce([monthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})})
+ .mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return '';})};})}).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr]).mockReturnValueOnce([noDataAttr])
+ .mockReturnValueOnce([monthNames]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})}).mockReturnValueOnce([seasonNames]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([seasonColor]).mockReturnValueOnce([seasonColor])
+ .mockReturnValueOnce([monthNames]).mockReturnValueOnce([sameMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'custom';})};})}).mockReturnValueOnce([seasonNames]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonColor]).mockReturnValueOnce([seasonColor])
+ .mockReturnValueOnce([monthNames]).mockReturnValueOnce([sameMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'custom';})};})}).mockReturnValueOnce([seasonNames]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonColor]).mockReturnValueOnce([seasonColor])
+ .mockReturnValueOnce([monthNames]).mockReturnValueOnce([monthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})}).mockReturnValueOnce([seasonNames]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonColor]).mockReturnValueOnce([seasonColor])
+ .mockReturnValueOnce([monthNames]).mockReturnValueOnce([invalidMonthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})}).mockReturnValueOnce([seasonNames]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonColor]).mockReturnValueOnce([seasonColor])
+ .mockReturnValueOnce([monthNames]).mockReturnValueOnce([monthDays]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([intercalary]).mockReturnValueOnce([monthLeapDays]).mockReturnValueOnce([weekdayNames]).mockReturnValueOnce({find: jest.fn(()=>{return {val: jest.fn(() => {return 'none';})};})}).mockReturnValueOnce([seasonNames]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonStart]).mockReturnValueOnce([seasonColor]).mockReturnValueOnce([seasonColor])
};
}
@@ -69,6 +80,7 @@ class FormApplication extends Application{
activateEditor(a: string){}
saveEditor(a: string){}
+ bringToTop(){}
}
// @ts-ignore
diff --git a/__mocks__/game.ts b/__mocks__/game.ts
index d0f721e0..02761892 100644
--- a/__mocks__/game.ts
+++ b/__mocks__/game.ts
@@ -1,7 +1,7 @@
/**
* This file mocks the FoundryVTT game global so that it can be used in testing
*/
-import {SettingNames} from "../src/constants";
+import {GameWorldTimeIntegrations, MoonIcons, MoonYearResetOptions, SettingNames} from "../src/constants";
//@ts-ignore
const local: Localization = {
@@ -39,12 +39,14 @@ const game = {
data: null,
i18n: local,
user: user,
+ paused: true,
// @ts-ignore
settings: {
get: jest.fn((moduleName: string, settingName: string): any => {
switch (settingName){
case SettingNames.AllowPlayersToAddNotes:
case SettingNames.DefaultNoteVisibility:
+ case SettingNames.ImportRan:
return false;
case SettingNames.YearConfiguration:
return {numericRepresentation: 0, prefix: '', postfix: '', showWeekdayHeadings: true};
@@ -55,17 +57,52 @@ const game = {
case SettingNames.LeapYearRule:
return {rule: 'none', customMod: 0};
case SettingNames.CurrentDate:
- return {year: 0, month: 1, day: 2};
+ return {year: 0, month: 1, day: 2, seconds: 3};
case SettingNames.Notes:
return [[{year: 0, month: 1, day: 2, title:'', content:'', author:'', playerVisible: false, id: 'abc123'}]];
+ case SettingNames.GeneralConfiguration:
+ return {gameWorldTimeIntegration: GameWorldTimeIntegrations.None, showClock: false, playersAddNotes: false}
+ case SettingNames.TimeConfiguration:
+ return {hoursInDay:0, minutesInHour: 1, secondsInMinute: 2, gameTimeRatio: 3};
+ case SettingNames.SeasonConfiguration:
+ return [[{name:'', startingMonth: 1, startingDay: 1, color: '#ffffff', customColor: ''}]];
+ case SettingNames.MoonConfiguration:
+ return [[{"name":"","cycleLength":0,"firstNewMoon":{"yearReset":"none","yearX":0,"year":0,"month":1,"day":1},"phases":[{"name":"","length":3.69,"icon":"new","singleDay":true}],"color":"#ffffff","cycleDayAdjust":0}]];
}
}),
register: jest.fn((moduleName: string, settingName: string, data: any) => {}),
set: jest.fn((moduleName: string, settingName: string, data: any) => {return Promise.resolve(true);})
+ },
+ time: {
+ worldTime: 10,
+ advance: jest.fn()
+ },
+ socket: {
+ on: jest.fn(),
+ emit: jest.fn()
+ },
+ combats: {
+ size: 0,
+ find: jest.fn((v)=>{
+ return v.call(undefined, {started: true});
+ })
+ },
+ modules: {
+ get: jest.fn()
+ },
+ Gametime: {
+ DTC: {
+ saveUserCalendar: jest.fn()
+ }
+ },
+ users: {
+ get: jest.fn(),
+ find: jest.fn((v)=>{
+ return v.call(undefined, {isGM: false, active: true});
+ })
}
- //keyboard: null,
- //modules: null
};
+
// @ts-ignore
global.game = game;
diff --git a/docs/Configuration.md b/docs/Configuration.md
new file mode 100644
index 00000000..827adc32
--- /dev/null
+++ b/docs/Configuration.md
@@ -0,0 +1,203 @@
+# Configuring Your calendar
+
+Configuration of the calendar is straight forward. As the GM open the configuration dialog and start customizing your calendar!
+
+## General Settings
+
+This tab allows you to set some general settings for the entire calendar.
+
+### Quick Setup
+
+#### Predefined Calendars
+
+This setting lets you choose from a list of predefined calendars to get your calendar started with. The following calendars can be selected to configure the game calendar:
+
+Calendar|Description|Initial Date
+--------|-----------|-------------
+Gregorian|This the standard real life calendar|The current date will be used
+Eberron| This is the calendar from the Eberron setting for Dungeons and Dragons | Zarantyr 1, 998 YK
+Exandrian |This is the calendar from the Exandria setting for Dungeons and Dragons | Horisal 1, 812 P.D.
+Golarian | This is the calendar from the Pathfinder game | Abadius 1, 4710 AR
+Greyhawk | This is the calendar from the Greyhawk setting for Dungeons and Dragons | Needfest 1, 591 cy
+Harptos | This is the calendar used across Faerun in the Forgotten Realms | Hammer 1, 1495 DR
+Warhammer | This is the calendar used by the Imperium in the Fantasy Warhammer game | Hexenstag 1, 2522
+
+All of these calendars can be further customized after they are loaded. They are here to provide a simple starting point for your game.
+
+### Game World Time Integration
+
+These settings dictate how Simple Calendar interacts with Foundry's game world time (the in game clock). Most other modules that have timed events or deal with time tie into the game world time, so it is a great way to keep everything in sync. The different settings for how Simple Calendar can interact with the game world time are:
+
+Option|Description|Update Game World Time|When Game World Time is Updated
+--------|--------------------|-------------------------------------------------|----------------------------------------------------------
+None (default)|Simple Calendar does not interact with the game world time at all. This setting is ideal if you want to keep Simple Calendar isolated from other modules.|Does not update the game world time|Simple Calendar is not updated when the game world time is updated by something else.
+Self|Treats Simple Calendar as the authority source for the game world time. This setting is ideal when you want Simple Calendar to be in control of the games time and don't want other modules updating Simple Calendar|Updates the game world time to match what is in Simple Calendar.|Combat round changes will update Simple Calendars time. Simple Calendar will ignore updates from all others modules.
+Third Party Module|This will instruct Simple Calendar to just display the Time in the game world time. All date changing controls are disabled and the changing of time relies 100% on another module. This setting is ideal if you are just want to use Simple Calenar to display the date in calendar form and/or take advantage of the notes.|Does not update the game world time.|Updates it's display everytime the game world time is changed, following what the other modules say the time is.
+Mixed|This option is a blend of the self and third party options. Simple calendar can change the game world time and and changes made by other modules are reflected in Simple Calendar. This setting is ideal if you want to use Simple Calendar and another module to change the game time.|Will update the game world time|Will update it's own time based on changes to the game world time, following what other modules say the time is.
+
+The most common interaction with another module is likely to be with Calendar/Weather. For this module I recommend using the "Self" or "Mixed" setting. With self weather effects will still trigger from Calendar/Weather as you advance time in Simple Calendar. Only use mixed if you also want to be able to use the Calendar/Weather controls to advance time to certain points (like dawn or dusk).
+
+#### Show Clock
+
+This setting is used to show the time clock below the calendar or to hide it. Not all games care about keeping track of the specific time of day so this is a great option to disable that part. Hiding the clock also hides the controls for changing hours, minutes, seconds.
+
+### Notes
+
+These are the settings pertaining to notes.
+
+Setting | Description
+-------- | ----------
+Note Default Player Visibility | For new notes, if by default the player visibility option is checked or not.
+Players Can Add Notes | If checked players will be allowed to add their own notes to the calendar.
+
+### Third Party Module Import/Export
+
+If you have certain other modules installed and active in your game, options will appear here to either import their settings into Simple Calendar or to export Simple Calendars settings into that module.
+The current supported modules for importing/exporting settings:
+
+#### about-time
+
+The [about-time](https://foundryvtt.com/packages/about-time) module is used for many other modules but can also be used on its own.
+
+Settings can be imported and exported between these two modules without issue.
+
+#### Calendar/Weather
+
+The [Calendar/Weather](https://foundryvtt.com/packages/calendar-weather) module is used as a way to integrate about-time with a custom calendar and additional weather effects.
+
+Most settings can be imported and exported between these two modules with these notable exceptions:
+
+- Calendar/Weather does not seem to support Leap Years at all (see this [line of code](https://github.com/DasSauerkraut/calendar-weather/blob/89b59e047c86c979b246ae385c471f4e824eaaa1/modules/dateTime.mjs#L42)) as a result:
+ - When Importing from Calendar/Weather no leap year rule will be set in Simple Calendar.
+ - When Exporting to Calendar/Weather the date will be off by the number of leap days that have passed so far. Example if there have been 4 leap years with 1 extra day each year the Calendar/Weather's calendar will be ahead by 4 days. This gets very exaggerated when using a Gregorian calendar for today's date as there have been 490 extra leap days for 2021.
+- Calendar/Weather's season colors and Simple Calendar's season colors do not line up so they are not Imported or Exported.
+- Calendar/Weather's season month is not properly stored:
+ - When Importing from Calendar/Weather every season will have its month set to be the first month.
+ - When Exporting to Calendar/Weather every season will have no month set.
+
+
+## Year Settings
+
+This tab allows you to change some settings about the years in your game world
+
+### Current Year
+
+This section is for setting information about the current year. The settings that are available to change are listed below:
+
+Setting | Description
+-------- | ----------
+Current Year | The Current year your game world is in. This can be any positive number.
+Year Prefix | Text that will appear before the year number.
+Year Postfix | Text that will appear after the year number.
+
+### Seasons
+
+This section displays all the seasons that exist in the calendar.
+Seasons are optional but can provide a nice thematic for your world.
+The options available for customizing each Season are listed below:
+
+Setting | Description
+--------|------------
+Season Name | These text boxes for each season allow you to change the name of an existing season.
+Starting Month | These drop downs for each season show all of the months of your calendar and allow you to choose which month this season starts in.
+Starting Day | These drop downs for each season show all of the days for the selected Starting Month and allow you to choose with day the season starts on in that month.
+Color | Seasons can be assigned a color, this color is used as the background color for the calendar display when it is the current season. There is a list of predefined colors that work well for standard season and the option to enter a custom color.
+Custom Color | If the color option is set to Custom Color this text box will appear where you can enter a custom Hex representation of a color to use for the season.
+Remove Button | These buttons for each season allow you to remove the specific season from the list.
+Add New Season Button | This button will add a new season to the bottom of the list with a default name that you can then configure to your liking.
+Remove All Seasons Button | This button will remove all of the seasons from the list.
+
+## Month Settings
+
+This tab displays all the months that exist in the calendar. Here you can change month names, the number of days in a month, remove a month, add a new month or remove all months.
+
+Setting | Description
+-------- | ----------
+Month Name | These text boxes for each month allow you to change the name of an existing month.
+Number of Days | These text boxes for each month allow you to change the number of days in each month. A month can have a minimum of 0 days.
+Intercalary Month | An intercalary month is one that does not follow the standard month numbering and is skipped.
Example: If we were to add an intercalary month between January and February, January would still be considered the first month and February would be considered the second month. The new month does not get a number.
Intercalary months also do not count towards the years total days nor do they affect the day of the week subsequent months start on.
+Include Intercalary Month In Total Day Count | When you select a month to be intercalary, an option will show to include these days as part of the years total days and have its days afect the day of the week subsequent months start on. The month though still is not numbered.
+Remove Button | These buttons for each month allow you to remove the month from the list.
+Add New Month Button | This button will add a new month to the bottom of the list with a default name and number of days that you can then configure to your liking.
+Remove All Months Button | This button will remove all of the months from the list.
+
+
+## Weekday Settings
+
+This section displays all the weekdays that exist in the calendar. Weekdays are used to determine how wide the calendar display should be and how to make month days to each day of the week.
+
+Setting | Description
+-------- | ----------
+Weekday Name | These text boxes for each weekday allow you to change the name of an existing weekday.
+Remove Button | These buttons for each weekday allow you to remove the weekday from the list.
+Add New Weekday Button | This button will add a new weekday to the bottom of the list with a default name that you can then configure to your liking.
+Remove All Weekdays Button | This button will remove all of the weekdays from the list.
+
+## Leap Year Settings
+
+This section allows the GM to configure how leap years work for this calendar.
+
+Setting | Description
+-------- | ----------
+Leap Year Rule | Which ruleset to follow when determining leap years. The options are - None: The calendar contains no leap years
- Gregorian: The calendars leap year rules are like the standard calendar (Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these years are leap years if they are exactly divisible by 400)
- Custom: Allows you to specify n interval in years for when a leap year happens.
+When Leap Years Happen | **This only appears if the Custom leap year rule is selected**.
The number of years when a leap year occurs. Example a value of 5 would mean every 5th year is a leap year.
+Months
+After you have changed the settings to your liking don't forget to save the configuration by hitting the Save Configuration button!
+List | **This only appears if the Custom or Gregorian leap year rule is selected**.
A list of months will appear that shows each month, and a textbox where you can change the number of days the corresponding month has during a leap year. A month can have a minimum of 0 leap year days.
+
+
+## Time Settings
+
+This section allows the GM to configure how hours, minutes and seconds work in their world.
+
+Setting | Description
+-------- | ----------
+Hours in a Day | This defines how many hours make up a single day.
+Minutes in a Hour | This defines how many minutes make up a single hour.
+Seconds in a Minute | This defines how many seconds make up a single minute.
+Game Seconds Per Real Life Seconds | This is used to determine how quickly game time advances when running the Simple Calendar clock. With a value of 1, for every real life seconds 1 second passes in the game. With a value of 2 for every 1 real life seconds 2 seconds pass in game. This does support decimals for more specific control.
+
+
+## Moon Settings
+
+This section allows the GM to configure the different moons of the world so that their cycles display on the calendar.
+
+There are lots of settings when configuring a moon to make it as customized as you would like.
+
+### Main Settings
+
+Settings | Description
+---------|------------
+Moon Name | The name of the moon, used to help distinguish between moons when there are more than one. Will show when hovering over a moon on the calendar view.
+Cycle Length | How many days it takes the moon to go from new moon to new moon. This field supports decimal places to get as precise as needed.
+Cycle Adjustment | When calculating how many days into a cycle a given date is, adjust that by this many days. This value will most likely be a decimal of less than 1 as adjustments do not need to be that large but supports any number.
+Moon Color | A color to associate with the moon. This is used to color the moon icons on the calendar view and helps distinguish between multiple moons. This is a hex color value.
+Remove | Will remove this moon from the list of moons.
+Add New Moon | Will add a moon to the list of moons.
+Remove All Moons | Will remove all moons from the list of moons.
+
+### Reference New Moon Settings
+
+This group of settings is used to tell the Calendar of when a New Moon occurred so that it can base when others will happen based off of that date.
+
+Settings | Description
+---------|------------
+Reference Moon Year Reset | This can be used to tell the calendar to reset the year portion of the reference new moon. Options are:
- **Do not reset reference year**: this will always use the entered year as the reference year.
- **Reset reference year every leap year**: This will reset the reference year being used to the year of the most recent leap year. Some calendars (Harptos) have their moons cycle reset on leap years.
- **Reset reference year every X years**: The same as the reset every leap year but instead will reset every X number of years entered. This is handy if the reference years need to reset and there are no leap years or they reset on a different schedule than leap years.
+New Moon Year| **Important**: This only shows when the Reference Moon Year Reset is set to "Do not reset reference year". This is the year of the reference new moon.
+Reset Reference Moon Years | **Important**: This only shows when the Reference Moon Year Reset is set to "Reset reference year every X years". This is how often, in years, to reset the reference year.
+New Moon Month | A drop down of all the months in the calendar where you can choose which month the new moon took place in.
+New Moon Day | A drop down of all the days for the selected new moon month where you can choose which day the new moon took place on.
+
+### Phases
+
+This section allows you to customize the different phases of the moon. Generally there will be no need to change the defaults for this, but the option is there in case it is required.
+
+Settings | Description
+---------|------------
+Phase Name | The name of the phase.
+Phase Length | The calculated number of days this phase will last based on the total number of phases and the moons cycle length.
+Phase Single Day | If this phase should only happen on 1 day, rather than over several days. This is used from the important moon phases like full moon.
+Phase Icon | Select from a list of available icons to use when displaying this phase of the moon.
+Remove | Removes this phase from the moons list of phases.
+Add New Moon Phase | Adds a new phase to the list of phases for this moon.
+Remove All Moon Phases | Removes all phases from the list of phases for this moon.
diff --git a/docs/Macros.md b/docs/Macros.md
new file mode 100644
index 00000000..d1d5f689
--- /dev/null
+++ b/docs/Macros.md
@@ -0,0 +1,33 @@
+# Macro's
+
+Here are all the exposed functions that can be used when creating a macro.
+
+## Open Simple Calendar
+You can create macros that will open up the Simple Calendar interface when used. To start create a new script macro and enter this as the command:
+
+```javascript
+SimpleCalendar.show();
+```
+
+**Important**: If this macro is intended to be useable by players don't forget to configure the Macros permissions for all players. It will need to be set to at least the "Limited", permission level.
+
+The show function can take 3 parameters to set the year, month and day that the calendar opens up to.
+
+Parameter|Type|Default|Details
+---------|----|-------|-------
+Year | number or null | null | The year to open the calendar too. If null is passed in it will open the calendar to the year the user last viewed
+Month | number or null | null | The month to open the calendar too.
The month is expected to start at 0, or be the index of the month to show. This way intercalary months can be easily chosen by using their index as they don't have a month number.
-1 can be passed in to view the last month of the year.
If null is passed in the calendar will open to the month the user last viewed.
+Day | number or null | null | The day of the month to select.
The day is expected to start at 1.
-1 can be passed in to select the last day of the month.
If null is passed in the selected day will be the last day the user selected, if any.
+
+## Examples
+All these examples assume we are using a standard Gregorian calendar.
+
+Open the calendar to August 2003
+```javascript
+SimpleCalendar.show(2003, 7);
+```
+
+Open the calendar to December 1999 and select the 25th day
+```javascript
+SimpleCalendar.show(1999, 11, 25);
+```
diff --git a/docs/Notes.md b/docs/Notes.md
new file mode 100644
index 00000000..bab398d7
--- /dev/null
+++ b/docs/Notes.md
@@ -0,0 +1,37 @@
+#Notes
+
+All information around viewing, adding, editing and removing notes.
+
+## Note Details
+
+The note details shows all the information about a specific note: the date the note is for, if the note repeats and how often it does, the title of the note, the author of the note and the content of the note.
+To view the note details select a day with the note icon on it and click on a note from the list below the calendar.
+
+![Calendar Button Location](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/note-view.png)
+
+
+## Adding, Editing and Removing notes
+
+The GM and players (if allowed by the GM) have the ability to add new notes to a day in Simple Calendar by clicking on the "Add New Note" button for a selected day.
+This will open a dialog where the details of the note can be filled out.
+
+![Calendar Button Location](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/note-new.png)
+
+Field | Description
+------- | -----------
+Note Title | The title for the note, this title will appear in the listing of notes for the day.
+Player Visible | If this note can be seen by players or if it is for the GM only. If a player is adding the note this option is disabled and checked by default.
+Note Repeats | If this note repeats weekly, monthly, yearly or never. If the note repeats monthly and it is on a day that some months don't have (eg the 31st) months that don't go to that day will not have this note.
+Details | Here you can enter the details of a note using the built in text editor.
+
+After all the details are filled out you can save the note.
+
+**Important**: If you have not saved the content in the text editor using the text editor save button, a warning will appear when you try to save the note letting you know.
+
+Players are able to edit or remove any notes they have added, the GM is able to edit or remove any note.
+To do this click on an existing note, at the bottom of the note dialog 2 additional buttons will be visible, Edit and Delete.
+
+Button | Description
+------- | -----------
+The Edit button | This will load the contents of the note in the same editor as creating a new note.
+The Delete button | This will open up a confirmation dialog, where selecting delete again will permanently remove the note.
diff --git a/docs/UpdatingDateTime.md b/docs/UpdatingDateTime.md
new file mode 100644
index 00000000..a35b0f65
--- /dev/null
+++ b/docs/UpdatingDateTime.md
@@ -0,0 +1,43 @@
+# Updating the Current Date
+The GM version of the module looks a little different, with the addition of controls to change the current date and a button to enter the configuration.
+
+![GM View](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/gm-view.png)
+
+The above image helps shows the controls, they are detailed out below.
+
+## Time Controls
+
+These controls are specific to controlling the time of the current day. As well as starting/stopping the built-in clock.
+
+**Important**: These controls will not display if the [Simple Calendar's game world time integration](./Configuration.md#game-world-time-integration) is configured to set to "Third Party" OR if the [Show Clock setting](./Configuration.md#show-clock) is unchecked.
+
+The top controls are used to select a unit of time (second, minute or hour) that will be changed by the buttons below.
+
+These buttons are used to move time forward or backward by the amount listed on the button (1 or 5)
+
+Simple Calendar has a built-in clock that can be used to automatically advance time as real world time passes by. This clock is started or stopped by the GM using the start/stop buttons under the time controls. Check out [Simple Calendars clock](./UsingTheCalendar.md#simple-calendars-clock) for more information.
+
+
+## Date Controls
+
+These controls are specific to controlling the date, day/month/year, of the calendar.
+
+**Important**: These controls will not display if the [Simple Calendar's game world time integration](./Configuration.md#game-world-time-integration) is configured to set to "Third Party".
+
+Control | Description
+------- | -----------
+Day Back/Forward | This moves the current day forward or back one day.
+Month Back/Forward | This moves the current month forward or back one month. The current day will be mapped to the same day as the old month, or the last day of the month if the old month has more days.
+Year Back/Forward | This moves the current year forward or back one year. The current month and day will stay the same in the new year.
+Apply | This will apply the changes, saving the new current day in the settings and updating all of the players calendars to reflect the new current day.
+
+
+
+## Other Controls
+
+These are additional controls that only the GM can access but are not specific to one of the above categories.
+
+Control | Description
+------- | -----------
+Configuration | This opens up the configuration dialog to allow the GM to fully customize the calendar.
+Add New Note Button | This will open the add notes dialog to add a new note for the selected day.
diff --git a/docs/UsingTheCalendar.md b/docs/UsingTheCalendar.md
new file mode 100644
index 00000000..d159a057
--- /dev/null
+++ b/docs/UsingTheCalendar.md
@@ -0,0 +1,35 @@
+# Accessing and Using the Calendar
+The module adds a calendar button to the basic controls section of the layer controls. Clicking on this will open the module window
+
+![Calendar Button Location](https://raw.githubusercontent.com/vigoren/foundryvtt-simple-calendar/main/docs/images/layers-button.png)
+
+The above image helps shows the controls, they are detailed out below.
+
+Control | Description
+------- | -----------
+Previous/Next | Allow the user to change which month/year they are currently viewing.
+Today Button | Changes the calendar so that the current day (in the game world) is visible and selected.
+Blue Circle Day | This indicates the current day in the game world, can be changed by the GM
+Green Circle Day | This indicates the day the user currently has selected. This will show any notes on this day.
+Red Indicator | This shows on any days that have notes that the user can see. It will show the number of notes on that day up to 99.
+Season Name | Appearing below the month and year if season are set up which season it current is will show.
+Calendar Background Color | If seasons are set up the background color of the calendar will change to match which season is being viewed.
+Moon Icons | Icons showing which phase of the moon will show on the bottom right of a day.
+Current Time | If the GM wants to display a clock of the current game time it will appear below the calendar and show the current time of day.
+Clock Icon | Next to the current time is a clock icon, this icon is used to indicate if the built in clock is running or not. More information [below](#simple-calendars-clock)!
+Notes List | Any notes that appear in this list can be clicked on to open the note details.
+Add New Note | If the GM allows players to add notes this button will appear in which a note can be added to the selected day.
+
+## Simple Calendars Clock
+
+Simple Calendar can now display a clock below the calendar. If the clock is not showing up, it is likely that the GM has turned off the clock for the current game as it will be not needed.
+
+The clock can be updated manually by the GM, or it can be enabled to update as real world time passes. The amount of in game time that passes as real world time passes is configurable by the GM.
+
+Once the clock has been started, it will begin incrementing time. The clock updates every 30 real life seconds, this is to allow time for proper syncing of the game time between all players.
+
+The clock icon next to the Current Time display will also update based on the state of the Simple Calendars clock.
+
+- **Red**: Means that Simple Calendars Clock is currently stopped and not automatically incrementing.
+- **Yellow**: Means that Simple Calendars Clock has been started but because the game is paused OR there is an active combat the clock is not updating.
+- **Green**: The clock will begin its animation, this means that Simple Calendars Clock is running and updating the game time as real time passes.
diff --git a/docs/images/gm-view.png b/docs/images/gm-view.png
index 70c5c93d..e57a5080 100644
Binary files a/docs/images/gm-view.png and b/docs/images/gm-view.png differ
diff --git a/docs/images/layers-button.png b/docs/images/layers-button.png
index f8d685a4..83e4dd24 100644
Binary files a/docs/images/layers-button.png and b/docs/images/layers-button.png differ
diff --git a/docs/images/logo.png b/docs/images/logo.png
index f9a404c9..8ee5bc7b 100644
Binary files a/docs/images/logo.png and b/docs/images/logo.png differ
diff --git a/docs/images/note-view.png b/docs/images/note-view.png
index 1c745ea8..a7c564bb 100644
Binary files a/docs/images/note-view.png and b/docs/images/note-view.png differ
diff --git a/package-lock.json b/package-lock.json
index 54fba69d..5d2d0b49 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "foundryvtt-simple-calendar",
- "version": "1.1.6",
+ "version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -630,9 +630,9 @@
}
},
"@league-of-foundry-developers/foundry-vtt-types": {
- "version": "0.7.9-0",
- "resolved": "https://registry.npmjs.org/@league-of-foundry-developers/foundry-vtt-types/-/foundry-vtt-types-0.7.9-0.tgz",
- "integrity": "sha512-67by3XlXKOyQVqmvVp0+kQ+JYUy+NcXAev3LMhl6OnqGEK8r/mVkGsKbpP45pEJQR8/P5AzHKv24botQZBfakg==",
+ "version": "0.7.9-5",
+ "resolved": "https://registry.npmjs.org/@league-of-foundry-developers/foundry-vtt-types/-/foundry-vtt-types-0.7.9-5.tgz",
+ "integrity": "sha512-ac5caa/etFAaNyLU1wJ35r/qLXvjNB/D4j5gqdFWLk8fZsGaZwD0ljNB8xj2ClvbGesZtiH0Nd9ajDVL07pQcQ==",
"dev": true,
"requires": {
"@types/howler": "2.2.1",
@@ -645,15 +645,6 @@
"typescript": "^4.1.4"
},
"dependencies": {
- "@types/jquery": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz",
- "integrity": "sha512-Tyctjh56U7eX2b9udu3wG853ASYP0uagChJcQJXLUXEU6C/JiW5qt5dl8ao01VRj1i5pgXPAf8f1mq4+FDLRQg==",
- "dev": true,
- "requires": {
- "@types/sizzle": "*"
- }
- },
"typescript": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
@@ -1131,18 +1122,18 @@
}
},
"@types/copy-webpack-plugin": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-6.4.0.tgz",
- "integrity": "sha512-f5mQG5c7xH3zLGrEmKgzLLFSGNB7Y4+4a+a1X4DvjgfbTEWEZUNNXUqGs5tBVCtb5qKPzm2z+6ixX3xirWmOCg==",
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz",
+ "integrity": "sha512-jnM0aMsaMTBr+xlMIO/fu+ZXIbSncmj4UB9ZHTXVfZJsUwGqtdfdSfz1/S8O99R9k7G5V6KhbAd8+QL0f2kUkg==",
"dev": true,
"requires": {
- "@types/webpack": "*"
+ "@types/webpack": "^4"
}
},
"@types/eslint": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
- "integrity": "sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw==",
+ "version": "7.2.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.7.tgz",
+ "integrity": "sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q==",
"dev": true,
"requires": {
"@types/estree": "*",
@@ -1215,15 +1206,24 @@
}
},
"@types/jest": {
- "version": "26.0.20",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz",
- "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==",
+ "version": "26.0.22",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz",
+ "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==",
"dev": true,
"requires": {
"jest-diff": "^26.0.0",
"pretty-format": "^26.0.0"
}
},
+ "@types/jquery": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz",
+ "integrity": "sha512-Tyctjh56U7eX2b9udu3wG853ASYP0uagChJcQJXLUXEU6C/JiW5qt5dl8ao01VRj1i5pgXPAf8f1mq4+FDLRQg==",
+ "dev": true,
+ "requires": {
+ "@types/sizzle": "*"
+ }
+ },
"@types/json-schema": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
@@ -1243,9 +1243,9 @@
"dev": true
},
"@types/node": {
- "version": "14.14.31",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz",
- "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==",
+ "version": "14.14.36",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.36.tgz",
+ "integrity": "sha512-kjivUwDJfIjngzbhooRnOLhGYz6oRFi+L+EpMjxroDYXwDw9lHrJJ43E+dJ6KAd3V3WxWAJ/qZE9XKYHhjPOFQ==",
"dev": true
},
"@types/normalize-package-data": {
@@ -1267,9 +1267,9 @@
"dev": true
},
"@types/socket.io-client": {
- "version": "1.4.35",
- "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.35.tgz",
- "integrity": "sha512-MI8YmxFS+jMkIziycT5ickBWK1sZwDwy16mgH/j99Mcom6zRG/NimNGQ3vJV0uX5G6g/hEw0FG3w3b3sT5OUGw==",
+ "version": "1.4.36",
+ "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.36.tgz",
+ "integrity": "sha512-ZJWjtFBeBy1kRSYpVbeGYTElf6BqPQUkXDlHHD4k/42byCN5Rh027f4yARHCink9sKAkbtGZXEAmR0ZCnc2/Ag==",
"dev": true
},
"@types/source-list-map": {
@@ -1961,9 +1961,9 @@
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001185",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz",
- "integrity": "sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==",
+ "version": "1.0.30001204",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz",
+ "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==",
"dev": true
},
"capture-exit": {
@@ -2175,14 +2175,14 @@
"dev": true
},
"copy-webpack-plugin": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-8.0.0.tgz",
- "integrity": "sha512-sqGe2FsB67wV/De+sz5azQklADe4thN016od6m7iK9KbjrSc1SEgg5QZ0LN+jGx5aZR52CbuXbqOhoIbqzzXlA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-8.1.0.tgz",
+ "integrity": "sha512-Soiq8kXI2AZkpw3dSp18u6oU2JonC7UKv3UdXsKOmT1A5QT46ku9+6c0Qy29JDbSavQJNN1/eKGpd3QNw+cZWg==",
"dev": true,
"requires": {
"fast-glob": "^3.2.5",
"glob-parent": "^5.1.1",
- "globby": "^11.0.2",
+ "globby": "^11.0.3",
"normalize-path": "^3.0.0",
"p-limit": "^3.1.0",
"schema-utils": "^3.0.0",
@@ -2278,16 +2278,16 @@
}
},
"css-loader": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.1.1.tgz",
- "integrity": "sha512-5FfhpjwtuRgxqmusDidowqmLlcb+1HgnEDMsi2JhiUrZUcoc+cqw+mUtMIF/+OfeMYaaFCLYp1TaIt9H6I/fKA==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.0.tgz",
+ "integrity": "sha512-MfRo2MjEeLXMlUkeUwN71Vx5oc6EJnx5UQ4Yi9iUtYQvrPtwLUucYptz0hc6n++kdNcyF5olYBS4vPjJDAcLkw==",
"dev": true,
"requires": {
"camelcase": "^6.2.0",
"cssesc": "^3.0.0",
"icss-utils": "^5.1.0",
"loader-utils": "^2.0.0",
- "postcss": "^8.2.6",
+ "postcss": "^8.2.8",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
"postcss-modules-scope": "^3.0.0",
@@ -2304,9 +2304,9 @@
"dev": true
},
"semver": {
- "version": "7.3.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
- "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -2566,9 +2566,9 @@
}
},
"electron-to-chromium": {
- "version": "1.3.662",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.662.tgz",
- "integrity": "sha512-IGBXmTGwdVGUVTnZ8ISEvkhDfhhD+CDFndG4//BhvDcEtPYiVrzoB+rzT/Y12OQCf5bvRCrVmrUbGrS9P7a6FQ==",
+ "version": "1.3.701",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.701.tgz",
+ "integrity": "sha512-Zd9ofdIMYHYhG1gvnejQDvC/kqSeXQvtXF0yRURGxgwGqDZm9F9Fm3dYFnm5gyuA7xpXfBlzVLN1sz0FjxpKfw==",
"dev": true
},
"emittery": {
@@ -2642,9 +2642,9 @@
}
},
"es-module-lexer": {
- "version": "0.3.26",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz",
- "integrity": "sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz",
+ "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==",
"dev": true
},
"es6-promise-polyfill": {
@@ -2730,9 +2730,9 @@
"dev": true
},
"events": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
- "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true
},
"exec-sh": {
@@ -3128,9 +3128,9 @@
"dev": true
},
"globby": {
- "version": "11.0.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz",
- "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==",
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
+ "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
@@ -4495,9 +4495,9 @@
"dev": true
},
"mini-css-extract-plugin": {
- "version": "1.3.9",
- "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.9.tgz",
- "integrity": "sha512-Ac4s+xhVbqlyhXS5J/Vh/QXUz3ycXlCqoCPpg0vdfhsIBH9eg/It/9L1r1XhSCH737M1lqcWnMuWL13zcygn5A==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.4.0.tgz",
+ "integrity": "sha512-DyQr5DhXXARKZoc4kwvCvD95kh69dUupfuKOmBUqZ4kBTmRaRZcU32lYu3cLd6nEGXhQ1l7LzZ3F/CjItaY6VQ==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
@@ -4572,9 +4572,9 @@
"dev": true
},
"nanoid": {
- "version": "3.1.20",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
- "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==",
+ "version": "3.1.22",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz",
+ "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==",
"dev": true
},
"nanomatch": {
@@ -4664,9 +4664,9 @@
}
},
"node-releases": {
- "version": "1.1.70",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz",
- "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==",
+ "version": "1.1.71",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
+ "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
"dev": true
},
"normalize-package-data": {
@@ -5009,14 +5009,22 @@
"dev": true
},
"postcss": {
- "version": "8.2.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.6.tgz",
- "integrity": "sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==",
+ "version": "8.2.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.8.tgz",
+ "integrity": "sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==",
"dev": true,
"requires": {
- "colorette": "^1.2.1",
+ "colorette": "^1.2.2",
"nanoid": "^3.1.20",
"source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "colorette": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
+ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
+ "dev": true
+ }
}
},
"postcss-modules-extract-imports": {
@@ -5147,9 +5155,9 @@
"dev": true
},
"queue-microtask": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz",
- "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"randombytes": {
@@ -6096,9 +6104,9 @@
}
},
"terser": {
- "version": "5.5.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz",
- "integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==",
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
+ "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -6236,9 +6244,9 @@
}
},
"ts-jest": {
- "version": "26.5.3",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.3.tgz",
- "integrity": "sha512-nBiiFGNvtujdLryU7MiMQh1iPmnZ/QvOskBbD2kURiI1MwqvxlxNnaAB/z9TbslMqCsSbu5BXvSSQPc5tvHGeA==",
+ "version": "26.5.4",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz",
+ "integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==",
"dev": true,
"requires": {
"bs-logger": "0.x",
@@ -6263,17 +6271,17 @@
}
},
"yargs-parser": {
- "version": "20.2.6",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.6.tgz",
- "integrity": "sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==",
+ "version": "20.2.7",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
+ "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==",
"dev": true
}
}
},
"ts-loader": {
- "version": "8.0.17",
- "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.17.tgz",
- "integrity": "sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w==",
+ "version": "8.0.18",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.18.tgz",
+ "integrity": "sha512-hRZzkydPX30XkLaQwJTDcWDoxZHK6IrEMDQpNd7tgcakFruFkeUp/aY+9hBb7BUGb+ZWKI0jiOGMo0MckwzdDQ==",
"dev": true,
"requires": {
"chalk": "^4.1.0",
@@ -6412,9 +6420,9 @@
"dev": true
},
"uglify-js": {
- "version": "3.13.0",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.0.tgz",
- "integrity": "sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==",
+ "version": "3.13.2",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.2.tgz",
+ "integrity": "sha512-SbMu4D2Vo95LMC/MetNaso1194M1htEA+JrqE9Hk+G2DhI+itfS9TRu9ZKeCahLDNa/J3n4MqUJ/fOHMzQpRWw==",
"dev": true,
"optional": true
},
@@ -6618,9 +6626,9 @@
"dev": true
},
"webpack": {
- "version": "5.21.2",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.21.2.tgz",
- "integrity": "sha512-xHflCenx+AM4uWKX71SWHhxml5aMXdy2tu/vdi4lClm7PADKxlyDAFFN1rEFzNV0MAoPpHtBeJnl/+K6F4QBPg==",
+ "version": "5.28.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.28.0.tgz",
+ "integrity": "sha512-1xllYVmA4dIvRjHzwELgW4KjIU1fW4PEuEnjsylz7k7H5HgPOctIq7W1jrt3sKH9yG5d72//XWzsHhfoWvsQVg==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",
@@ -6632,7 +6640,7 @@
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.7.0",
- "es-module-lexer": "^0.3.26",
+ "es-module-lexer": "^0.4.0",
"eslint-scope": "^5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
@@ -6649,9 +6657,9 @@
},
"dependencies": {
"acorn": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz",
- "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz",
+ "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==",
"dev": true
}
}
diff --git a/package.json b/package.json
index ce2635f2..a5b5e97b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "foundryvtt-simple-calendar",
"description": "A simple calendar module for keeping track of game days and events.",
- "version": "1.1.8",
+ "version": "1.2.0",
"author": "Dean Vigoren (vigorator)",
"keywords": [
"foundryvtt",
@@ -23,25 +23,25 @@
"test": "jest"
},
"devDependencies": {
- "@league-of-foundry-developers/foundry-vtt-types": "^0.7.9-0",
- "@types/copy-webpack-plugin": "^6.4.0",
- "@types/jest": "^26.0.20",
- "@types/node": "^14.14.31",
+ "@league-of-foundry-developers/foundry-vtt-types": "^0.7.9-5",
+ "@types/copy-webpack-plugin": "^6.4.1",
+ "@types/jest": "^26.0.22",
+ "@types/node": "^14.14.36",
"clean-webpack-plugin": "^3.0.0",
- "copy-webpack-plugin": "^8.0.0",
+ "copy-webpack-plugin": "^8.1.0",
"cross-env": "^7.0.3",
- "css-loader": "^5.1.1",
+ "css-loader": "^5.2.0",
"jest": "^26.6.3",
"jest-ts-webcompat-resolver": "^1.0.0",
- "mini-css-extract-plugin": "^1.3.9",
+ "mini-css-extract-plugin": "^1.4.0",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
- "ts-jest": "^26.5.3",
- "ts-loader": "^8.0.17",
+ "ts-jest": "^26.5.4",
+ "ts-loader": "^8.0.18",
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.1.3",
- "webpack": "^5.21.2",
+ "webpack": "^5.28.0",
"webpack-cli": "^4.5.0"
}
}
diff --git a/src/classes/game-settings.test.ts b/src/classes/game-settings.test.ts
index e3a4c5f3..89213c9c 100644
--- a/src/classes/game-settings.test.ts
+++ b/src/classes/game-settings.test.ts
@@ -13,8 +13,12 @@ import Month from "./month";
import {Weekday} from "./weekday";
import {Note} from "./note";
import LeapYear from "./leap-year";
-import {LeapYearRules} from "../constants";
+import {GameWorldTimeIntegrations, LeapYearRules, MoonIcons, MoonYearResetOptions} from "../constants";
+import {GeneralSettings, TimeConfig} from "../interfaces";
import Mock = jest.Mock;
+import Time from "./time";
+import Season from "./season";
+import Moon from "./moon";
describe('Game Settings Class Tests', () => {
@@ -62,7 +66,12 @@ describe('Game Settings Class Tests', () => {
SimpleCalendar.instance = new SimpleCalendar();
GameSettings.RegisterSettings();
expect(game.settings.register).toHaveBeenCalled();
- expect(game.settings.register).toHaveBeenCalledTimes(7);
+ expect(game.settings.register).toHaveBeenCalledTimes(12);
+ });
+
+ test('Get Import Ran', () => {
+ expect(GameSettings.GetImportRan()).toBe(false);
+ expect(game.settings.get).toHaveBeenCalled();
});
test('Get Default Note Visibility', () => {
@@ -70,13 +79,18 @@ describe('Game Settings Class Tests', () => {
expect(game.settings.get).toHaveBeenCalled();
});
+ test('Load General Settings', () => {
+ expect(GameSettings.LoadGeneralSettings()).toStrictEqual({gameWorldTimeIntegration: GameWorldTimeIntegrations.None, showClock: false, playersAddNotes: false});
+ expect(game.settings.get).toHaveBeenCalled();
+ });
+
test('Load Year Data', () => {
expect(GameSettings.LoadYearData()).toStrictEqual({numericRepresentation: 0, prefix: '', postfix: '', showWeekdayHeadings: true});
expect(game.settings.get).toHaveBeenCalled();
});
test('Load Current Date', () => {
- expect(GameSettings.LoadCurrentDate()).toStrictEqual({year: 0, month: 1, day: 2});
+ expect(GameSettings.LoadCurrentDate()).toStrictEqual({year: 0, month: 1, day: 2, seconds: 3});
expect(game.settings.get).toHaveBeenCalled();
});
@@ -102,11 +116,38 @@ describe('Game Settings Class Tests', () => {
expect(GameSettings.LoadWeekdayData()).toStrictEqual([]);
});
+ test('Load Season Data', () => {
+ expect(GameSettings.LoadSeasonData()).toStrictEqual([{name:'', startingMonth: 1, startingDay: 1, color: '#ffffff', customColor: ''}]);
+ expect(game.settings.get).toHaveBeenCalled();
+ (game.settings.get).mockReturnValueOnce(false);
+ expect(GameSettings.LoadSeasonData()).toStrictEqual([]);
+ (game.settings.get).mockReturnValueOnce([]);
+ expect(GameSettings.LoadSeasonData()).toStrictEqual([]);
+ (game.settings.get).mockReturnValueOnce([false]);
+ expect(GameSettings.LoadSeasonData()).toStrictEqual([]);
+ });
+
+ test('Load Moon Data', () => {
+ expect(GameSettings.LoadMoonData()).toStrictEqual([{"name":"","cycleLength":0,"firstNewMoon":{"yearReset":"none","yearX":0,"year":0,"month":1,"day":1},"phases":[{"name":"","length":3.69,"icon":"new","singleDay":true}],"color":"#ffffff","cycleDayAdjust":0}]);
+ expect(game.settings.get).toHaveBeenCalled();
+ (game.settings.get).mockReturnValueOnce(false);
+ expect(GameSettings.LoadMoonData()).toStrictEqual([]);
+ (game.settings.get).mockReturnValueOnce([]);
+ expect(GameSettings.LoadMoonData()).toStrictEqual([]);
+ (game.settings.get).mockReturnValueOnce([false]);
+ expect(GameSettings.LoadMoonData()).toStrictEqual([]);
+ });
+
test('Load Leap Year Rule', () => {
expect(GameSettings.LoadLeapYearRules()).toStrictEqual({rule: 'none', customMod: 0});
expect(game.settings.get).toHaveBeenCalled();
});
+ test('Load Time Data', () => {
+ expect(GameSettings.LoadTimeData()).toStrictEqual({hoursInDay:0, minutesInHour: 1, secondsInMinute: 2, gameTimeRatio: 3});
+ expect(game.settings.get).toHaveBeenCalled();
+ });
+
test('Load Notes', () => {
expect(GameSettings.LoadNotes()).toStrictEqual([{year: 0, month: 1, day: 2, title:'', content:'', author:'', playerVisible: false, id: "abc123"}]);
expect(game.settings.get).toHaveBeenCalled();
@@ -118,11 +159,38 @@ describe('Game Settings Class Tests', () => {
expect(GameSettings.LoadNotes()).toStrictEqual([]);
});
+ test('Set Import Ran', () => {
+ // @ts-ignore
+ game.user.isGM = false;
+ expect(GameSettings.SetImportRan(true)).resolves.toBe(false);
+ // @ts-ignore
+ game.user.isGM = true;
+ expect(GameSettings.SetImportRan(true)).resolves.toBe(true);
+ expect(game.settings.set).toHaveBeenCalled();
+ });
+
+ test('Save General Settings', () => {
+ // @ts-ignore
+ game.user.isGM = false;
+ let gs: GeneralSettings = {gameWorldTimeIntegration: GameWorldTimeIntegrations.None, showClock: false, playersAddNotes: false};
+ expect(GameSettings.SaveGeneralSettings(gs)).resolves.toBe(false);
+ // @ts-ignore
+ game.user.isGM = true;
+ expect(GameSettings.SaveGeneralSettings(gs)).resolves.toBe(false);
+
+ gs.showClock = true;
+ expect(GameSettings.SaveGeneralSettings(gs)).resolves.toBe(true);
+ expect(game.settings.set).toHaveBeenCalled();
+ });
+
test('Save Current Date', () => {
jest.spyOn(console, 'error').mockImplementation();
+ // @ts-ignore
+ game.user.isGM = false;
const year = new Year(0);
const month = new Month('T', 1, 10);
year.months.push(month);
+ year.time.seconds = 3;
expect(GameSettings.SaveCurrentDate(year)).resolves.toBe(false);
expect(console.error).toHaveBeenCalledTimes(1);
// @ts-ignore
@@ -197,6 +265,35 @@ describe('Game Settings Class Tests', () => {
expect(game.settings.set).toHaveBeenCalledTimes(1);
});
+ test('Save Season Configuration', () => {
+ // @ts-ignore
+ game.user.isGM = false;
+ const season = new Season('', 1, 1);
+ season.customColor = '';
+ expect(GameSettings.SaveSeasonConfiguration([season])).resolves.toBe(false);
+ // @ts-ignore
+ game.user.isGM = true;
+ expect(GameSettings.SaveSeasonConfiguration([season])).resolves.toBe(false);
+ expect(game.settings.set).toHaveBeenCalledTimes(0);
+ season.name = 'Spring';
+ expect(GameSettings.SaveSeasonConfiguration([season])).resolves.toBe(true);
+ expect(game.settings.set).toHaveBeenCalledTimes(1);
+ });
+
+ test('Save Moon Configuration', () => {
+ // @ts-ignore
+ game.user.isGM = false;
+ const moon = new Moon('', 0);
+ expect(GameSettings.SaveMoonConfiguration([moon])).resolves.toBe(false);
+ // @ts-ignore
+ game.user.isGM = true;
+ expect(GameSettings.SaveMoonConfiguration([moon])).resolves.toBe(false);
+ expect(game.settings.set).toHaveBeenCalledTimes(0);
+ moon.name = "Moon";
+ expect(GameSettings.SaveMoonConfiguration([moon])).resolves.toBe(false);
+ expect(game.settings.set).toHaveBeenCalledTimes(1);
+ });
+
test('Save Leap Year Rule', () => {
// @ts-ignore
game.user.isGM = false;
@@ -212,13 +309,33 @@ describe('Game Settings Class Tests', () => {
expect(game.settings.set).toHaveBeenCalledTimes(1);
});
- test('Save Notes', () => {
+ test('Save Time Configuration', () => {
+ // @ts-ignore
+ game.user.isGM = false;
+ let gs = new Time(0, 1, 2);
+ gs.gameTimeRatio = 3;
+ expect(GameSettings.SaveTimeConfiguration(gs)).resolves.toBe(false);
+ // @ts-ignore
+ game.user.isGM = true;
+ expect(GameSettings.SaveTimeConfiguration(gs)).resolves.toBe(false);
+
+ gs.gameTimeRatio = 4;
+ expect(GameSettings.SaveTimeConfiguration(gs)).resolves.toBe(true);
+ expect(game.settings.set).toHaveBeenCalled();
+ });
+
+ test('Save Notes', async () => {
const note = new Note();
note.year = 0;
note.month = 1;
note.day = 2;
- expect(GameSettings.SaveNotes([note])).resolves.toBe(true);
- expect(game.settings.set).toHaveBeenCalled();
+ await GameSettings.SaveNotes([note]);
+ expect(game.settings.set).toHaveBeenCalledTimes(1);
+ // @ts-ignore
+ game.user.isGM = false;
+ await GameSettings.SaveNotes([note]);
+ expect(game.settings.set).toHaveBeenCalledTimes(1);
+ expect(game.socket.emit).toHaveBeenCalledTimes(1);
});
test('Set Default Note Visibility', () => {
diff --git a/src/classes/game-settings.ts b/src/classes/game-settings.ts
index 0066cdbe..097c48ad 100644
--- a/src/classes/game-settings.ts
+++ b/src/classes/game-settings.ts
@@ -1,12 +1,23 @@
import Year from "./year";
import {Logger} from "./logging";
-import {CurrentDateConfig, MonthConfig, WeekdayConfig, YearConfig, NoteConfig, LeapYearConfig} from "../interfaces";
-import {ModuleName, SettingNames} from "../constants";
+import {
+ CurrentDateConfig,
+ MonthConfig,
+ WeekdayConfig,
+ YearConfig,
+ NoteConfig,
+ LeapYearConfig,
+ TimeConfig, GeneralSettings, SimpleCalendarSocket, SeasonConfiguration, MoonConfiguration
+} from "../interfaces";
+import {ModuleName, ModuleSocketName, SettingNames, SocketTypes} from "../constants";
import SimpleCalendar from "./simple-calendar";
import Month from "./month";
import {Weekday} from "./weekday";
import {Note} from "./note";
import LeapYear from "./leap-year";
+import Time from "./time";
+import Season from "./season";
+import Moon from "./moon";
export class GameSettings {
/**
@@ -45,6 +56,13 @@ export class GameSettings {
* Register the settings this module needs to use with the game
*/
static RegisterSettings(){
+ game.settings.register(ModuleName, SettingNames.GeneralConfiguration, {
+ name: "General Configuration",
+ scope: "world",
+ config: false,
+ type: Object,
+ onChange: SimpleCalendar.instance.settingUpdate.bind(SimpleCalendar.instance, true, 'general')
+ });
game.settings.register(ModuleName, SettingNames.YearConfiguration, {
name: "Year Configuration",
scope: "world",
@@ -97,6 +115,44 @@ export class GameSettings {
default: [],
onChange: SimpleCalendar.instance.loadNotes.bind(SimpleCalendar.instance, true)
});
+ game.settings.register(ModuleName, SettingNames.TimeConfiguration, {
+ name: "Time",
+ scope: "world",
+ config: false,
+ type: Object,
+ default: {},
+ onChange: SimpleCalendar.instance.settingUpdate.bind(SimpleCalendar.instance, true, 'time')
+ });
+ game.settings.register(ModuleName, SettingNames.ImportRan, {
+ name: "Import",
+ scope: "world",
+ config: false,
+ type: Boolean,
+ default: false
+ });
+ game.settings.register(ModuleName, SettingNames.SeasonConfiguration, {
+ name: "Season Configuration",
+ scope: "world",
+ config: false,
+ type: Array,
+ default: [],
+ onChange: SimpleCalendar.instance.settingUpdate.bind(SimpleCalendar.instance, true, 'season')
+ });
+ game.settings.register(ModuleName, SettingNames.MoonConfiguration, {
+ name: "Moon Configuration",
+ scope: "world",
+ config: false,
+ type: Array,
+ default: [],
+ onChange: SimpleCalendar.instance.settingUpdate.bind(SimpleCalendar.instance, true, 'moon')
+ });
+ }
+
+ /**
+ * Gets if the import question has been run for modules
+ */
+ static GetImportRan(){
+ return game.settings.get(ModuleName, SettingNames.ImportRan);
}
/**
@@ -107,12 +163,19 @@ export class GameSettings {
return game.settings.get(ModuleName, SettingNames.DefaultNoteVisibility);
}
+ /**
+ * Loads the general settings from the game world settings
+ */
+ static LoadGeneralSettings(): GeneralSettings {
+ return game.settings.get(ModuleName, SettingNames.GeneralConfiguration);
+ }
+
/**
* Loads the year configuration from the game world settings
* @return {YearConfig}
*/
static LoadYearData(): YearConfig {
- return game.settings.get(ModuleName, SettingNames.YearConfiguration);
+ return game.settings.get(ModuleName, SettingNames.YearConfiguration);
}
/**
@@ -120,7 +183,7 @@ export class GameSettings {
* @return {Array.}
*/
static LoadCurrentDate(): CurrentDateConfig {
- return game.settings.get(ModuleName, SettingNames.CurrentDate);
+ return game.settings.get(ModuleName, SettingNames.CurrentDate);
}
/**
@@ -153,12 +216,49 @@ export class GameSettings {
return returnData;
}
+ /**
+ * Loads the season configuration from the game world settings
+ * @return {Array.}
+ */
+ static LoadSeasonData(): SeasonConfiguration[] {
+ let returnData: SeasonConfiguration[] = [];
+ let seasonData = game.settings.get(ModuleName, SettingNames.SeasonConfiguration);
+ if(seasonData && seasonData.length) {
+ if (Array.isArray(seasonData[0])) {
+ returnData = seasonData[0];
+ }
+ }
+ return returnData;
+ }
+
+ /**
+ * Loads the moon configuration from the game world settings
+ * @return {Array.}
+ */
+ static LoadMoonData(): MoonConfiguration[] {
+ let returnData: MoonConfiguration[] = [];
+ let moonData = game.settings.get(ModuleName, SettingNames.MoonConfiguration);
+ if(moonData && moonData.length) {
+ if (Array.isArray(moonData[0])) {
+ returnData = moonData[0];
+ }
+ }
+ return returnData;
+ }
+
/**
* Loads the leap year rules from the settings
* @return {LeapYearConfig}
*/
static LoadLeapYearRules(): LeapYearConfig {
- return game.settings.get(ModuleName, SettingNames.LeapYearRule);
+ return game.settings.get(ModuleName, SettingNames.LeapYearRule);
+ }
+
+ /**
+ * Loads the time configuration from the game world settings
+ */
+ static LoadTimeData(): TimeConfig {
+ return game.settings.get(ModuleName, SettingNames.TimeConfiguration);
}
/**
@@ -176,6 +276,35 @@ export class GameSettings {
return returnData;
}
+ /**
+ * Sets the import ran setting
+ * @param {boolean} ran If the import was ran/asked about
+ */
+ static async SetImportRan(ran: boolean){
+ if(GameSettings.IsGm()){
+ await game.settings.set(ModuleName, SettingNames.ImportRan, ran);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Saves the general settings to the world settings
+ * @param {GeneralSettings} settings The settings to save
+ */
+ static async SaveGeneralSettings(settings: GeneralSettings): Promise {
+ if(GameSettings.IsGm()){
+ Logger.debug('Saving General Settings.');
+ const currentSettings = GameSettings.LoadGeneralSettings();
+ if(JSON.stringify(settings) !== JSON.stringify(currentSettings)){
+ return game.settings.set(ModuleName, SettingNames.GeneralConfiguration, settings).then(()=>{return true;});
+ } else {
+ Logger.debug('General Settings have not changed, not updating.');
+ }
+ }
+ return false
+ }
+
/**
* Saves the current date to the world settings
* @param {Year} year The year that has the current date
@@ -191,9 +320,10 @@ export class GameSettings {
const newDate: CurrentDateConfig = {
year: year.numericRepresentation,
month: currentMonth.numericRepresentation,
- day: currentDay.numericRepresentation
+ day: currentDay.numericRepresentation,
+ seconds: year.time.seconds
};
- if(currentDate.year !== newDate.year || currentDate.month !== newDate.month || currentDate.day !== newDate.day){
+ if(currentDate.year !== newDate.year || currentDate.month !== newDate.month || currentDate.day !== newDate.day || currentDate.seconds !== newDate.seconds){
return game.settings.set(ModuleName, SettingNames.CurrentDate, newDate);
} else {
Logger.debug('Current Date data has not changed, not updating settings');
@@ -280,6 +410,49 @@ export class GameSettings {
return false;
}
+ /**
+ * Saves the passed in season configuration in the world settings
+ * @param {Array.>} seasons List of seasons
+ */
+ static async SaveSeasonConfiguration(seasons: Season[]): Promise {
+ if(GameSettings.IsGm()){
+ Logger.debug('Saving season configuration.');
+ const currentConfig = JSON.stringify(GameSettings.LoadSeasonData());
+ const newConfig: SeasonConfiguration[] = seasons.map(s => {return {name: s.name, startingMonth: s.startingMonth, startingDay: s.startingDay, color: s.color, customColor: s.customColor}});
+ if(currentConfig !== JSON.stringify(newConfig)){
+ return game.settings.set(ModuleName, SettingNames.SeasonConfiguration, newConfig).then(() => {return true;});
+ } else {
+ Logger.debug('Season configuration has not changed, not updating.');
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Saves the passed in moon configuration in the world settings
+ * @param {Array.} moons List of moons
+ */
+ static async SaveMoonConfiguration(moons: Moon[]): Promise {
+ if(GameSettings.IsGm()){
+ Logger.debug('Saving moon configuration.');
+ const currentConfig = JSON.stringify(GameSettings.LoadMoonData());
+ const newConfig: MoonConfiguration[] = moons.map(m => {return {
+ name: m.name,
+ cycleLength: m.cycleLength,
+ firstNewMoon: m.firstNewMoon,
+ phases: m.phases,
+ color: m.color,
+ cycleDayAdjust: m.cycleDayAdjust
+ };});
+ if(currentConfig !== JSON.stringify(newConfig)){
+ return game.settings.set(ModuleName, SettingNames.MoonConfiguration, newConfig).then(() => {return true;});
+ } else {
+ Logger.debug('Moon configuration has not changed, not updating.');
+ }
+ }
+ return false;
+ }
+
/**
* Saves the passed in leap year configuration into the world settings
* @param {LeapYear} leapYear The leap year settings to save
@@ -302,6 +475,29 @@ export class GameSettings {
return false;
}
+ /**
+ * Saves the time configuration into the world settings
+ * @param {Time} time The time object to save
+ */
+ static async SaveTimeConfiguration(time: Time): Promise {
+ if(game.user && game.user.isGM) {
+ Logger.debug(`Saving time configuration.`);
+ const current = GameSettings.LoadTimeData();
+ const newtc: TimeConfig = {
+ hoursInDay: time.hoursInDay,
+ minutesInHour: time.minutesInHour,
+ secondsInMinute: time.secondsInMinute,
+ gameTimeRatio: time.gameTimeRatio
+ };
+ if(JSON.stringify(current) !== JSON.stringify(newtc)){
+ return game.settings.set(ModuleName, SettingNames.TimeConfiguration, newtc).then(() => { return true });
+ } else {
+ Logger.debug('Time configuration has not changed, not updating settings');
+ }
+ }
+ return false;
+ }
+
/**
* Saves the passed in notes into the world settings
* @param {Array.} notes The notes to save
@@ -320,7 +516,15 @@ export class GameSettings {
id: w.id,
repeats: w.repeats
};});
- return game.settings.set(ModuleName, SettingNames.Notes, newConfig).then(() => {return true;});
+ if(GameSettings.IsGm()){
+ return game.settings.set(ModuleName, SettingNames.Notes, newConfig).then(() => {return true;});
+ } else {
+ const socketData = {type: SocketTypes.journal, data: {notes: notes}};
+ Logger.debug(`User saving notes...`);
+ await game.socket.emit(ModuleSocketName, socketData);
+ return;
+ }
+
}
/**
diff --git a/src/classes/handlebars-helpers.test.ts b/src/classes/handlebars-helpers.test.ts
index 7d149996..f193db2b 100644
--- a/src/classes/handlebars-helpers.test.ts
+++ b/src/classes/handlebars-helpers.test.ts
@@ -20,7 +20,7 @@ describe('Handlebars Helpers Tests', () => {
test('Register', () => {
HandlebarsHelpers.Register();
- expect(Handlebars.registerHelper).toHaveBeenCalledTimes(3);
+ expect(Handlebars.registerHelper).toHaveBeenCalledTimes(4);
});
test('Calendar Width', () => {
@@ -36,6 +36,11 @@ describe('Handlebars Helpers Tests', () => {
// @ts-ignore
game.user.isGM = true;
expect(HandlebarsHelpers.CalendarRowWidth()).toBe('width:352px;');
+ if(SimpleCalendar.instance.currentYear){
+ SimpleCalendar.instance.currentYear.generalSettings.showClock = true;
+ expect(HandlebarsHelpers.CalendarRowWidth()).toBe('width:550px;');
+ }
+
});
test('Day Has Notes', () => {
@@ -50,7 +55,7 @@ describe('Handlebars Helpers Tests', () => {
SimpleCalendar.instance.currentYear.months[0].visible = true;
expect(HandlebarsHelpers.DayHasNotes(options)).toBe('');
options.hash['day'].numericRepresentation = 2;
- expect(HandlebarsHelpers.DayHasNotes(options)).toBe(`1`);
+ expect(HandlebarsHelpers.DayHasNotes(options)).toBe(`1`);
for(let i = 0; i < 99; i++){
var n = new Note()
@@ -60,10 +65,23 @@ describe('Handlebars Helpers Tests', () => {
SimpleCalendar.instance.notes.push(n);
}
expect(SimpleCalendar.instance.notes.length).toBe(100);
- expect(HandlebarsHelpers.DayHasNotes(options)).toBe(`99`);
+ expect(HandlebarsHelpers.DayHasNotes(options)).toBe(`99`);
} else {
fail('Current year is not set');
}
});
+
+ test('Day Moon Phase', () => {
+ const options: any = {hash:{}};
+ expect(HandlebarsHelpers.DayMoonPhase(options)).toBe('');
+ options.hash['day'] = {numericRepresentation: 1};
+ expect(HandlebarsHelpers.DayMoonPhase(options)).toBe('');
+ SimpleCalendar.instance.settingUpdate();
+ if(SimpleCalendar.instance.currentYear){
+ expect(HandlebarsHelpers.DayMoonPhase(options)).toBe('');
+ SimpleCalendar.instance.currentYear.moons[0].phases[0].singleDay = false;
+ expect(HandlebarsHelpers.DayMoonPhase(options)).toBe('');
+ }
+ });
});
diff --git a/src/classes/handlebars-helpers.ts b/src/classes/handlebars-helpers.ts
index f8620528..f52b5d1c 100644
--- a/src/classes/handlebars-helpers.ts
+++ b/src/classes/handlebars-helpers.ts
@@ -13,6 +13,7 @@ export default class HandlebarsHelpers{
Handlebars.registerHelper("calendar-width", HandlebarsHelpers.CalendarWidth);
Handlebars.registerHelper("calendar-row-width", HandlebarsHelpers.CalendarRowWidth);
Handlebars.registerHelper("day-has-note", HandlebarsHelpers.DayHasNotes);
+ Handlebars.registerHelper("day-moon-phase", HandlebarsHelpers.DayMoonPhase);
}
/**
@@ -21,7 +22,8 @@ export default class HandlebarsHelpers{
*/
static CalendarWidth(){
if(SimpleCalendar.instance.currentYear){
- return `width:${(SimpleCalendar.instance.currentYear.weekdays.length * 40) + 12}px;`;
+ let width = (SimpleCalendar.instance.currentYear.weekdays.length * 40) + 12;
+ return `width:${width}px;`;
}
return '';
}
@@ -32,8 +34,11 @@ export default class HandlebarsHelpers{
*/
static CalendarRowWidth(){
if(GameSettings.IsGm() && SimpleCalendar.instance.currentYear){
- let width = (SimpleCalendar.instance.currentYear.weekdays.length * 40) + 312;
- return `width:${width}px;`;
+ let width = (SimpleCalendar.instance.currentYear.weekdays.length * 40) + 12;
+ if(SimpleCalendar.instance.currentYear.generalSettings.showClock && width < 250){
+ width = 250;
+ }
+ return `width:${width+300}px;`;
}
return '';
}
@@ -52,9 +57,29 @@ export default class HandlebarsHelpers{
const notes = SimpleCalendar.instance.notes.filter(n => n.isVisible(year, month.numericRepresentation, day));
if(notes.length){
const count = notes.length < 100? notes.length : 99;
- return `${count}`;
+ return `${count}`;
+ }
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Checks to see the current phase of the moon for the given day
+ * @param {*} options The options object passed from Handlebars
+ * @return {string}
+ */
+ static DayMoonPhase(options: any){
+ if(options.hash.hasOwnProperty('day') && SimpleCalendar.instance.currentYear){
+ const day = options.hash['day'];
+ let html = ''
+ for(let i = 0; i < SimpleCalendar.instance.currentYear.moons.length; i++){
+ const mp = SimpleCalendar.instance.currentYear.moons[i].getMoonPhase(SimpleCalendar.instance.currentYear, 'visible', day);
+ if(mp.singleDay || day.selected || day.current){
+ html += ``;
}
}
+ return html;
}
return '';
}
diff --git a/src/classes/importer.test.ts b/src/classes/importer.test.ts
new file mode 100644
index 00000000..db709d55
--- /dev/null
+++ b/src/classes/importer.test.ts
@@ -0,0 +1,251 @@
+/**
+ * @jest-environment jsdom
+ */
+import "../../__mocks__/game";
+import "../../__mocks__/form-application";
+import "../../__mocks__/application";
+import "../../__mocks__/handlebars";
+import "../../__mocks__/event";
+import "../../__mocks__/dialog";
+
+import Year from "./year";
+import Month from "./month";
+import Importer from "./importer";
+import {LeapYearRules} from "../constants";
+import {Weekday} from "./weekday";
+import Mock = jest.Mock;
+import Season from "./season";
+import Moon from "./moon";
+
+describe('Importer Class Tests', () => {
+ let y: Year;
+
+ beforeEach(() => {
+ y = new Year(0);
+ y.months.push(new Month('M', 1, 5));
+ y.months.push(new Month('T', 2, 15));
+ y.months.push(new Month('W', 3, 1));
+ y.months[2].intercalary = true;
+ y.weekdays.push(new Weekday(1, 'S'));
+ y.seasons.push(new Season('S', 1, 1));
+ y.moons.push(new Moon('M', 1));
+ // @ts-ignore
+ game.user.isGM = true;
+ });
+
+ test('Import About Time', () => {
+ const mockAboutTime = {
+ "clock_start_year": 1,
+ "first_day": 1,
+ "hours_per_day": 12,
+ "seconds_per_minute": 30,
+ "minutes_per_hour": 30,
+ "has_year_0": true,
+ "month_len": {
+ "Month 1": {
+ "days": [10,10],
+ "intercalary": false
+ },
+ "Month 2": {
+ "days": [10,11],
+ "intercalary": false
+ },
+ "Month 3": {
+ "days": [1,1],
+ "intercalary": true
+ }
+ },
+ "_month_len": {},
+ "weekdays": ["S","M","T"],
+ "leap_year_rule": '',
+ "notes": {}
+ };
+ (game.settings.get).mockReturnValueOnce(mockAboutTime);
+ Importer.importAboutTime(y);
+
+ expect(y.time.hoursInDay).toBe(12);
+ expect(y.time.minutesInHour).toBe(30);
+ expect(y.time.secondsInMinute).toBe(30);
+ expect(y.weekdays.length).toBe(3);
+ expect(y.weekdays[0].name).toBe('S');
+ expect(y.weekdays[1].name).toBe('M');
+ expect(y.weekdays[2].name).toBe('T');
+ expect(y.months.length).toBe(3);
+ expect(y.months[0].name).toBe('Month 1');
+ expect(y.months[0].numberOfDays).toBe(10);
+ expect(y.months[0].numberOfLeapYearDays).toBe(10);
+ expect(y.months[0].intercalary).toBe(false);
+ expect(y.months[1].name).toBe('Month 2');
+ expect(y.months[1].numberOfDays).toBe(10);
+ expect(y.months[1].numberOfLeapYearDays).toBe(11);
+ expect(y.months[1].intercalary).toBe(false);
+ expect(y.months[2].name).toBe('Month 3');
+ expect(y.months[2].numberOfDays).toBe(1);
+ expect(y.months[2].numberOfLeapYearDays).toBe(1);
+ expect(y.months[2].intercalary).toBe(true);
+ expect(y.leapYearRule.rule).toBe(LeapYearRules.None);
+
+ (game.settings.get).mockReturnValueOnce(mockAboutTime);
+ mockAboutTime.leap_year_rule = '(year) => Math.floor(year / 4) - Math.floor(year / 100) + Math.floor(year / 400)';
+ Importer.importAboutTime(y);
+ expect(y.leapYearRule.rule).toBe(LeapYearRules.Gregorian);
+
+ (game.settings.get).mockReturnValueOnce(mockAboutTime);
+ mockAboutTime.leap_year_rule = '(year) => Math.floor(year / 8 + 1)';
+ Importer.importAboutTime(y);
+ expect(y.leapYearRule.rule).toBe(LeapYearRules.Custom);
+ expect(y.leapYearRule.customMod).toBe(8);
+ });
+
+ test('Export About Time', async () => {
+ await Importer.exportToAboutTime(y);
+ //@ts-ignore
+ expect(game.Gametime.DTC.saveUserCalendar).toHaveBeenCalledTimes(1);
+
+ y.leapYearRule.rule = LeapYearRules.Gregorian;
+ (game.settings.get).mockReturnValueOnce(0);
+ await Importer.exportToAboutTime(y);
+ //@ts-ignore
+ expect(game.Gametime.DTC.saveUserCalendar).toHaveBeenCalledTimes(2);
+
+ y.leapYearRule.rule = LeapYearRules.Custom;
+ y.leapYearRule.customMod = 8;
+ await Importer.exportToAboutTime(y);
+ //@ts-ignore
+ expect(game.Gametime.DTC.saveUserCalendar).toHaveBeenCalledTimes(3);
+ });
+
+ test('Import Calendar Weather', () => {
+ const mockCalendarWeather = {
+ months: [
+ {
+ name: 'Month 1',
+ length: 10,
+ leapLength: 10,
+ isNumbered: true,
+ abbrev: ''
+ },
+ {
+ name: 'Month 2',
+ length: 10,
+ leapLength: 11,
+ isNumbered: true,
+ abbrev: ''
+ },
+ {
+ name: 'Month 3',
+ length: 1,
+ leapLength: 1,
+ isNumbered: false,
+ abbrev: ''
+ }
+ ],
+ daysOfTheWeek: ["S","M","T"],
+ year: 12,
+ day: 5,
+ numDayOfTheWeek: 2,
+ first_day: 0,
+ currentMonth: 2,
+ currentWeekday: 'M',
+ dateWordy: "",
+ era: "",
+ dayLength: 12,
+ timeDisp: '',
+ dateNum: '',
+ weather: {},
+ seasons: [
+ {
+ name: 'Spring',
+ rolltable: '',
+ date: {
+ month: '',
+ day: 1,
+ combined: '-1'
+ },
+ temp: '=',
+ humidity: '=',
+ color: 'red',
+ dawn: 6,
+ dusk: 19
+ }
+ ],
+ moons: [{
+ name:'moon',
+ cycleLength: 0,
+ cyclePercent: 0,
+ lunarEclipseChange: 0,
+ solarEclipseChange: 0,
+ referenceTime: 0,
+ referencePercent: 0,
+ }],
+ events: [],
+ reEvents: []
+ };
+
+ (game.settings.get).mockReturnValueOnce(mockCalendarWeather);
+ Importer.importCalendarWeather(y);
+
+ expect(y.time.hoursInDay).toBe(12);
+ expect(y.time.minutesInHour).toBe(60);
+ expect(y.time.secondsInMinute).toBe(60);
+ expect(y.weekdays.length).toBe(3);
+ expect(y.weekdays[0].name).toBe('S');
+ expect(y.weekdays[1].name).toBe('M');
+ expect(y.weekdays[2].name).toBe('T');
+ expect(y.months.length).toBe(3);
+ expect(y.months[0].name).toBe('Month 1');
+ expect(y.months[0].numberOfDays).toBe(10);
+ expect(y.months[0].numberOfLeapYearDays).toBe(10);
+ expect(y.months[0].intercalary).toBe(false);
+ expect(y.months[1].name).toBe('Month 2');
+ expect(y.months[1].numberOfDays).toBe(10);
+ expect(y.months[1].numberOfLeapYearDays).toBe(11);
+ expect(y.months[1].intercalary).toBe(false);
+ expect(y.months[2].name).toBe('Month 3');
+ expect(y.months[2].numberOfDays).toBe(1);
+ expect(y.months[2].numberOfLeapYearDays).toBe(1);
+ expect(y.months[2].intercalary).toBe(true);
+ expect(y.leapYearRule.rule).toBe(LeapYearRules.None);
+ });
+
+ test('Export Calendar Weather', async () => {
+ //@ts-ignore
+ delete window.location;
+ //@ts-ignore
+ window.location = {reload: jest.fn()};
+ const xport = {
+ months: [],
+ daysOfTheWeek: [],
+ year: 0,
+ currentMonth: 0,
+ day: 0,
+ numDayOfTheWeek: 0,
+ currentWeekday: '',
+ era: '',
+ dayLength: 0,
+ first_day: 0
+ };
+ (game.settings.get).mockReturnValueOnce(xport);
+ await Importer.exportCalendarWeather(y);
+
+ expect(xport.months.length).toBe(3);
+ expect(xport.daysOfTheWeek.length).toBe(1);
+ expect(xport.year).toBe(0);
+ expect(xport.currentMonth).toBe(0);
+ expect(xport.day).toBe(0);
+ expect(window.location.reload).toHaveBeenCalledTimes(1);
+
+ y.months[1].current = true;
+
+ (game.settings.get).mockReturnValueOnce(xport);
+ await Importer.exportCalendarWeather(y);
+ expect(xport.currentMonth).toBe(1);
+ expect(window.location.reload).toHaveBeenCalledTimes(2);
+
+ y.months[1].days[2].current = true;
+ (game.settings.get).mockReturnValueOnce(xport);
+ await Importer.exportCalendarWeather(y);
+ expect(xport.day).toBe(2);
+ expect(window.location.reload).toHaveBeenCalledTimes(3);
+ });
+});
diff --git a/src/classes/importer.ts b/src/classes/importer.ts
new file mode 100644
index 00000000..53c11d94
--- /dev/null
+++ b/src/classes/importer.ts
@@ -0,0 +1,263 @@
+import Year from "./year";
+import {AboutTimeImport, CalendarWeatherImport} from "../interfaces";
+import {Weekday} from "./weekday";
+import Month from "./month";
+import {LeapYearRules} from "../constants";
+import {GameSettings} from "./game-settings";
+import Season from "./season";
+import Moon from "./moon";
+
+export default class Importer{
+
+ /**
+ * Loads the about time calendar configuration into Simple Calendars configuration
+ * @param {Year} year The year to load the about time configuration into
+ */
+ static async importAboutTime(year: Year){
+ const aboutTimeCalendar = game.settings.get('about-time', 'savedCalendar');
+
+ //Set up the time parameters
+ year.time.hoursInDay = aboutTimeCalendar['hours_per_day'];
+ year.time.minutesInHour = aboutTimeCalendar['minutes_per_hour'];
+ year.time.secondsInMinute = aboutTimeCalendar['seconds_per_minute'];
+
+ //Set up the weekdays
+ year.weekdays = [];
+ for(let i = 0; i < aboutTimeCalendar['weekdays'].length; i++){
+ year.weekdays.push(new Weekday(i+1, aboutTimeCalendar['weekdays'][i]));
+ }
+
+ //Set up the months
+ year.months = [];
+ let mCount = 1;
+ let mICount = 1;
+ for(let key in aboutTimeCalendar['month_len']){
+ if(aboutTimeCalendar['month_len'].hasOwnProperty(key)){
+ const newM = new Month(key, mCount, aboutTimeCalendar['month_len'][key].days[0], aboutTimeCalendar['month_len'][key].days[1]);
+ if(aboutTimeCalendar['month_len'][key].intercalary){
+ newM.numericRepresentation = mICount * -1;
+ newM.intercalary = true;
+ newM.intercalaryInclude = false;
+ mICount++;
+ } else {
+ mCount++;
+ }
+ year.months.push(newM);
+ }
+ }
+
+ //Try to set up the leap year rule
+ year.leapYearRule.rule = LeapYearRules.None;
+ if(aboutTimeCalendar['leap_year_rule'] === '(year) => Math.floor(year / 4) - Math.floor(year / 100) + Math.floor(year / 400)') {
+ year.leapYearRule.rule = LeapYearRules.Gregorian;
+ } else {
+ const matches = aboutTimeCalendar['leap_year_rule'].match(/(\d)/g);
+ if(matches && matches.length && matches[0] !== '0'){
+ year.leapYearRule.rule = LeapYearRules.Custom;
+ year.leapYearRule.customMod = parseInt(matches[0]);
+ }
+ }
+
+ //Set the current time
+ const currentTime = year.secondsToDate(game.time.worldTime);
+ year.updateTime(currentTime);
+
+ //Save everything
+ await GameSettings.SaveYearConfiguration(year);
+ await GameSettings.SaveMonthConfiguration(year.months);
+ await GameSettings.SaveWeekdayConfiguration(year.weekdays);
+ await GameSettings.SaveLeapYearRules(year.leapYearRule);
+ await GameSettings.SaveTimeConfiguration(year.time);
+ await GameSettings.SaveCurrentDate(year);
+ }
+
+ /**
+ * Sets up about time to match Simple Calendars configuration
+ * @param {Year} year The year to use
+ *
+ * Known Issues:
+ * - Intercalary days seem to be calculated differently so calendars with them do not match up perfectly with Simple Calendar
+ */
+ static async exportToAboutTime(year: Year){
+
+ const monthList: AboutTimeImport.MonthList = {};
+ for(let i = 0; i < year.months.length; i++){
+ monthList[year.months[i].name] = {
+ days: [year.months[i].numberOfDays, year.months[i].numberOfLeapYearDays],
+ intercalary: year.months[i].intercalary
+ };
+ }
+
+ const newAboutTimeConfig: AboutTimeImport.Calendar = {
+ "first_day": 0,
+ "clock_start_year": 0,
+ "has_year_0": true,
+ "notes": {},
+ "hours_per_day": year.time.hoursInDay,
+ "minutes_per_hour": year.time.minutesInHour,
+ "seconds_per_minute": year.time.secondsInMinute,
+ "weekdays": year.weekdays.map( w => w.name),
+ "leap_year_rule": `(year) => 0`,
+ "month_len": monthList,
+ "_month_len": {}
+ };
+
+ if(year.leapYearRule.rule === LeapYearRules.Gregorian){
+ newAboutTimeConfig.leap_year_rule = `(year) => Math.floor(year / 4) - Math.floor(year / 100) + Math.floor(year / 400)`;
+ } else if(year.leapYearRule.rule === LeapYearRules.Custom){
+ //Gross but might be all we can do
+ newAboutTimeConfig.leap_year_rule = `(year) => Math.floor(year / ${year.leapYearRule.customMod} ) + 1`;
+ }
+ //@ts-ignore
+ game.Gametime.DTC.saveUserCalendar(newAboutTimeConfig);
+
+ // Ensure about time uses the new calendar on startup
+ if (game.settings.get("about-time", "calendar") !== 0){
+ await game.settings.set("about-time", "calendar", 0);
+ }
+ }
+
+ /**
+ * Loads the calendar/weather calendar configuration into Simple Calendars configuration
+ * @param {Year} year The year to load the about time configuration into
+ *
+ * Known Issues:
+ * - Seasons: The month is not properly stored by calendar-weather so all months are set to the first month
+ * - Seasons: The color of the seasons can not be transferred over
+ */
+ static async importCalendarWeather(year: Year){
+ const currentSettings = game.settings.get('calendar-weather', 'dateTime');
+
+ //Set up the time
+ year.time.hoursInDay = currentSettings.dayLength;
+
+ //Set up the weekdays
+ year.weekdays = [];
+ for(let i = 0; i < currentSettings.daysOfTheWeek.length; i++){
+ year.weekdays.push(new Weekday(i+1, currentSettings.daysOfTheWeek[i]));
+ }
+
+ //Set up the months
+ year.months = [];
+ let mCount = 1;
+ let mICount = 1;
+ for(let i = 0; i < currentSettings.months.length; i++){
+ const nMonth = new Month(currentSettings.months[i].name, i+1, currentSettings.months[i].length, currentSettings.months[i].leapLength)
+ if(!currentSettings.months[i].isNumbered){
+ nMonth.numericRepresentation = mICount * -1;
+ nMonth.intercalary = true;
+ nMonth.intercalaryInclude = false;
+ mICount++;
+ } else {
+ mCount++;
+ }
+ year.months.push(nMonth);
+ }
+
+ year.leapYearRule.rule = LeapYearRules.None;
+
+ //Set up the seasons
+ year.seasons = [];
+ for(let i = 0; i < currentSettings.seasons.length; i++){
+ const nSeason = new Season(currentSettings.seasons[i].name, 1, currentSettings.seasons[i].date.day + 1);
+ year.seasons.push(nSeason);
+ }
+
+ year.moons = [];
+ for(let i = 0; i< currentSettings.moons.length; i++){
+ const newMoon = new Moon(currentSettings.moons[i].name, currentSettings.moons[i].cycleLength);
+ const currentTime = year.secondsToDate(currentSettings.moons[i].referenceTime);
+ newMoon.firstNewMoon.year = currentTime.year;
+ newMoon.firstNewMoon.month = currentTime.month;
+ newMoon.firstNewMoon.day = currentTime.day;
+ }
+
+ //Set the current time
+ const currentTime = year.secondsToDate(game.time.worldTime);
+ year.updateTime(currentTime);
+
+ //Save everything
+ await GameSettings.SaveYearConfiguration(year);
+ await GameSettings.SaveMonthConfiguration(year.months);
+ await GameSettings.SaveWeekdayConfiguration(year.weekdays);
+ await GameSettings.SaveLeapYearRules(year.leapYearRule);
+ await GameSettings.SaveTimeConfiguration(year.time);
+ await GameSettings.SaveMoonConfiguration(year.moons);
+ await GameSettings.SaveCurrentDate(year);
+ }
+
+ /**
+ * Sets up calendar weather to match Simple Calendars configuration
+ * @param {Year} year The year to use
+ *
+ * Known Issues:
+ * - Calendar/Weather does not support leap years so any calendar that has leap years will not work properly and be out of sync with Simple Calendar
+ * - As About time is used as the base, the same known issues for that will apply here
+ * - Seasons: Simple Calendars colors do not exist in Calendar/Weather so they can not be exported
+ */
+ static async exportCalendarWeather(year: Year){
+ const currentSettings = game.settings.get('calendar-weather', 'dateTime');
+
+ const monthList: CalendarWeatherImport.Month[] = [];
+ for(let i = 0; i < year.months.length; i++){
+ monthList.push({
+ name: year.months[i].name,
+ length: year.months[i].numberOfDays,
+ leapLength: year.months[i].numberOfLeapYearDays,
+ isNumbered: !year.months[i].intercalary,
+ abbrev: year.months[i].intercalary? year.months[i].name.substring(0,2) : ''
+ });
+ }
+
+ const seasonList: CalendarWeatherImport.Seasons[] = [];
+ for(let i = 0; i < year.seasons.length; i++){
+ seasonList.push({
+ name: year.seasons[i].name,
+ color: '',
+ dawn: 6,
+ dusk: 19,
+ humidity: '=',
+ rolltable: '',
+ temp: '=',
+ date: {
+ day: year.seasons[i].startingDay - 1,
+ month: '',
+ combined: `-${year.seasons[i].startingDay - 1}`
+ }
+ });
+ }
+
+ const moonList: CalendarWeatherImport.Moons[] = [];
+ for(let i = 0; i < year.moons.length; i++){
+ moonList.push({
+ name: year.moons[i].name,
+ cycleLength: year.moons[i].cycleLength,
+ cyclePercent: 0,
+ lunarEclipseChange: 0,
+ solarEclipseChange: 0,
+ referencePercent: 0,
+ referenceTime: year.time.getTotalSeconds(year.dateToDays(year.moons[i].firstNewMoon.year, year.moons[i].firstNewMoon.month, year.moons[i].firstNewMoon.day, true, true) - 1)
+ });
+ }
+
+ const currentMonth = year.getMonth();
+ const currentDay = currentMonth?.getDay();
+ const weekDays = year.weekdays.map( w => w.name)
+ const dow = year.dayOfTheWeek(year.numericRepresentation, currentMonth? currentMonth.numericRepresentation : 1, currentDay? currentDay.numericRepresentation : 1);
+ currentSettings.months = monthList;
+ currentSettings.daysOfTheWeek = weekDays;
+ currentSettings.year = year.numericRepresentation;
+ currentSettings.currentMonth = currentMonth? currentMonth.numericRepresentation - 1 : 0;
+ currentSettings.day = currentDay? currentDay.numericRepresentation - 1 : 0;
+ currentSettings.numDayOfTheWeek = dow;
+ currentSettings.currentWeekday = weekDays[dow];
+ currentSettings.era = '';
+ currentSettings.dayLength = year.time.hoursInDay
+ currentSettings.first_day = 0;
+ currentSettings.seasons = seasonList;
+ currentSettings.moons = moonList;
+ await game.settings.set('calendar-weather', 'dateTime', currentSettings);
+ await Importer.exportToAboutTime(year);
+ window.location.reload();
+ }
+}
diff --git a/src/classes/leap-year.test.ts b/src/classes/leap-year.test.ts
index 6384681a..fc17da29 100644
--- a/src/classes/leap-year.test.ts
+++ b/src/classes/leap-year.test.ts
@@ -87,4 +87,22 @@ describe('Leap Year Tests', () => {
expect(lr.howManyLeapYears(2020)).toBe(403);
expect(lr.howManyLeapYears(2021)).toBe(404);
});
+
+ test('Previous Leap Year', () => {
+ expect(lr.previousLeapYear(1990)).toBe(null);
+ lr.rule = LeapYearRules.Gregorian;
+ expect(lr.previousLeapYear(1990)).toBe(1988);
+ });
+
+ test('Fraction', () => {
+ expect(lr.fraction(1990)).toBe(0);
+ lr.rule = LeapYearRules.Gregorian;
+ expect(lr.fraction(1990)).toBe(0.5);
+ lr.rule = LeapYearRules.Custom;
+ lr.customMod = 5;
+ expect(lr.fraction(1991)).toBe(0.2);
+
+ lr.customMod = 0;
+ expect(lr.fraction(1990)).toBe(0);
+ });
});
diff --git a/src/classes/leap-year.ts b/src/classes/leap-year.ts
index b0069c59..0da3695b 100644
--- a/src/classes/leap-year.ts
+++ b/src/classes/leap-year.ts
@@ -45,4 +45,32 @@ export default class LeapYear {
return num;
}
+ previousLeapYear(year: number): number | null {
+ if(this.rule === LeapYearRules.Gregorian || (this.rule === LeapYearRules.Custom && this.customMod !== 0)){
+ let testYear = year;
+ while(Number.isInteger(testYear)){
+ if(this.isLeapYear(testYear)){
+ break;
+ } else {
+ testYear--;
+ }
+ }
+ return testYear;
+ }
+ return null;
+ }
+
+ fraction(year: number){
+ const previousLeapYear = this.previousLeapYear(year);
+ if(previousLeapYear !== null){
+ const yearInto = year%previousLeapYear;
+ if(this.rule === LeapYearRules.Gregorian){
+ return yearInto / 4
+ } else {
+ return yearInto / this.customMod;
+ }
+ }
+ return 0;
+ }
+
}
diff --git a/src/classes/moon.test.ts b/src/classes/moon.test.ts
new file mode 100644
index 00000000..e87bea50
--- /dev/null
+++ b/src/classes/moon.test.ts
@@ -0,0 +1,96 @@
+/**
+ * @jest-environment jsdom
+ */
+import "../../__mocks__/game";
+import "../../__mocks__/form-application";
+import "../../__mocks__/application";
+import "../../__mocks__/handlebars";
+import "../../__mocks__/event";
+import "../../__mocks__/crypto";
+import SimpleCalendar from "./simple-calendar";
+import Year from "./year";
+import Month from "./month";
+import Moon from "./moon";
+import {LeapYearRules, MoonIcons, MoonYearResetOptions} from "../constants";
+
+describe('Moon Tests', () => {
+ let m :Moon;
+
+ beforeEach(() => {
+ m = new Moon("Moon", 29.5);
+ });
+
+ test('Properties', () => {
+ expect(Object.keys(m).length).toBe(6); //Make sure no new properties have been added
+ expect(m.name).toBe('Moon');
+ expect(m.cycleLength).toBe(29.5);
+ expect(m.cycleDayAdjust).toBe(0);
+ expect(m.color).toBe('#ffffff');
+ expect(m.phases.length).toBe(1);
+ expect(m.firstNewMoon).toStrictEqual({ "day": 1, "month": 1, "year": 0, "yearReset": "none", "yearX": 0 });
+ });
+
+ test('Clone', () => {
+ expect(m.clone()).toStrictEqual(m);
+ });
+
+ test('To Template', () => {
+ const y = new Year(0);
+ let c = m.toTemplate(y);
+ expect(Object.keys(c).length).toBe(7); //Make sure no new properties have been added
+ expect(m.name).toBe('Moon');
+ expect(m.cycleLength).toBe(29.5);
+ expect(m.firstNewMoon).toStrictEqual({ "day": 1, "month": 1, "year": 0, "yearReset": "none", "yearX": 0 });
+ expect(m.phases.length).toBe(1);
+ expect(m.color).toBe('#ffffff');
+ expect(m.cycleDayAdjust).toBe(0);
+ expect(c.dayList).toStrictEqual([]);
+
+ SimpleCalendar.instance = new SimpleCalendar();
+
+ c = m.toTemplate(y);
+ expect(c.dayList.length).toStrictEqual(0);
+ y.months.push(new Month("Month 1", 1, 10));
+ c = m.toTemplate(y);
+ expect(c.dayList.length).toStrictEqual(10);
+ });
+
+ test('Update Phase Length', () => {
+ m.updatePhaseLength();
+ expect(m.phases[0].length).toBe(1);
+
+ m.phases.push({name: 'p2', icon: MoonIcons.NewMoon, length: 0, singleDay: false});
+ m.updatePhaseLength();
+ expect(m.phases[0].length).toBe(1);
+ expect(m.phases[1].length).toBe(28.5);
+ });
+
+ test('Get Moon Phase', () => {
+ const y = new Year(0);
+ m.phases.push({name: 'p2', icon: MoonIcons.NewMoon, length: 0, singleDay: false});
+ m.updatePhaseLength();
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[0]);
+ y.months.push(new Month("Month 1", 1, 10));
+ y.months[0].current = true;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[0]);
+ y.months[0].days[2].current = true;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[1]);
+
+ m.firstNewMoon.yearReset = MoonYearResetOptions.LeapYear;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[1]);
+ y.leapYearRule.rule = LeapYearRules.Gregorian;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[1]);
+ y.numericRepresentation = 1990;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[1]);
+
+ m.firstNewMoon.yearReset = MoonYearResetOptions.XYears;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[0]);
+ m.firstNewMoon.yearX = 5;
+ expect(m.getMoonPhase(y)).toStrictEqual(m.phases[1]);
+
+ y.months[0].visible = true;
+ expect(m.getMoonPhase(y, 'visible', {numericRepresentation: 2, name: "2", current: false, selected: false})).toStrictEqual(m.phases[1]);
+
+ expect(m.getMoonPhase(y, 'selected')).toStrictEqual(m.phases[0]);
+ });
+});
diff --git a/src/classes/moon.ts b/src/classes/moon.ts
new file mode 100644
index 00000000..c527c157
--- /dev/null
+++ b/src/classes/moon.ts
@@ -0,0 +1,205 @@
+import {DayTemplate, FirstNewMoonDate, MoonPhase, MoonTemplate} from "../interfaces";
+import Year from "./year";
+import {MoonIcons, MoonYearResetOptions} from "../constants";
+import {Logger} from "./logging";
+
+/**
+ * Class for representing a moon
+ */
+export default class Moon{
+ /**
+ * The name of the moon
+ * @type {string}
+ */
+ name: string;
+ /**
+ * How long in calendar days the moon takes to do 1 revolution
+ * @type {number}
+ */
+ cycleLength: number;
+ /**
+ * The different phases of the moon
+ * @type {Array}
+ */
+ phases: MoonPhase[] = [];
+ /**
+ * When the first new moon took place. Used as a reference for calculating the position of the current cycle
+ */
+ firstNewMoon: FirstNewMoonDate = {
+ /**
+ * The year reset options for the first new moon
+ * @type {number}
+ */
+ yearReset: MoonYearResetOptions.None,
+ /**
+ * How often the year should reset
+ * @type {number}
+ */
+ yearX: 0,
+ /**
+ * The year of the first new moon
+ * @type {number}
+ */
+ year: 0,
+ /**
+ * The month of the first new moon
+ * @type {number}
+ */
+ month: 1,
+ /**
+ * The day of the first new moon
+ * @type {number}
+ */
+ day: 1
+ };
+ /**
+ * A color to associate with the moon when displaying it on the calendar
+ */
+ color: string = '#ffffff';
+ /**
+ * The amount of days to adjust the current cycle day by
+ * @type {number}
+ */
+ cycleDayAdjust: number = 0;
+
+ /**
+ * The moon constructor
+ * @param {string} name The name of the moon
+ * @param {number} cycleLength The length of the moons cycle
+ */
+ constructor(name: string, cycleLength: number) {
+ this.name = name;
+ this.cycleLength = cycleLength;
+
+ this.phases.push({
+ name: game.i18n.localize('FSC.Moon.Phase.New'),
+ length: 3.69,
+ icon: MoonIcons.NewMoon,
+ singleDay: true
+ });
+ }
+
+ /**
+ * Creates a clone of this moon object
+ * @return {Moon}
+ */
+ clone(): Moon {
+ const c = new Moon(this.name, this.cycleLength);
+ c.phases = this.phases.map(p => { return { name: p.name, length: p.length, icon: p.icon, singleDay: p.singleDay };});
+ c.firstNewMoon.yearReset = this.firstNewMoon.yearReset;
+ c.firstNewMoon.yearX = this.firstNewMoon.yearX;
+ c.firstNewMoon.year = this.firstNewMoon.year;
+ c.firstNewMoon.month = this.firstNewMoon.month;
+ c.firstNewMoon.day = this.firstNewMoon.day;
+ c.color = this.color;
+ c.cycleDayAdjust = this.cycleDayAdjust;
+ return c;
+ }
+
+ /**
+ * Converts this moon into a template used for displaying the moon in HTML
+ * @param {Year} year The year to use for getting the days and months
+ */
+ toTemplate(year: Year): MoonTemplate {
+ const data: MoonTemplate = {
+ name: this.name,
+ cycleLength: this.cycleLength,
+ firstNewMoon: this.firstNewMoon,
+ phases: this.phases,
+ color: this.color,
+ cycleDayAdjust: this.cycleDayAdjust,
+ dayList: []
+ };
+
+ const month = year.months.find(m => m.numericRepresentation === data.firstNewMoon.month);
+
+ if(month){
+ data.dayList = month.days.map(d => d.toTemplate());
+ }
+
+ return data;
+ }
+
+ /**
+ * Updates each phases length in days so the total length of all phases matches the cycle length
+ */
+ updatePhaseLength(){
+ let pLength = 0, singleDays = 0;
+ for(let i = 0; i < this.phases.length; i++){
+ if(this.phases[i].singleDay){
+ singleDays++;
+ } else {
+ pLength++;
+ }
+ }
+ const phaseLength = Number(((this.cycleLength - singleDays) / pLength).toPrecision(6));
+
+ this.phases.forEach(p => {
+ if(p.singleDay){
+ p.length = 1;
+ } else {
+ p.length = phaseLength;
+ }
+ });
+ }
+
+ /**
+ * Returns the current phase of the moon based on a year month and day.
+ * This phase will be within + or - 1 days of when the phase actually begins
+ * @param {Year} year The year class used to get the year, month and day to use
+ * @param {string} property Which property to use when getting the year, month and day. Can be current, selected or visible
+ * @param {DayTemplate|null} [dayToUse=null] The day to use instead of the day associated with the property
+ */
+ getMoonPhase(year: Year, property = 'current', dayToUse: DayTemplate | null = null): MoonPhase{
+ property = property.toLowerCase() as 'current' | 'selected' | 'visible';
+ let yearNum = property === 'current'? year.numericRepresentation : property === 'selected'? year.selectedYear : year.visibleYear;
+ let firstNewMoonDays = year.dateToDays(this.firstNewMoon.year, this.firstNewMoon.month, this.firstNewMoon.day, true, true) - 1;
+
+ const month = year.getMonth(property);
+ if(month){
+ const day = property !== 'visible'? month.getDay(property) : dayToUse;
+ let monthNum = month.numericRepresentation;
+ let dayNum = day? day.numericRepresentation : 1;
+ let resetYearAdjustment = 0;
+
+ if(this.firstNewMoon.yearReset === MoonYearResetOptions.LeapYear){
+ let lyYear = year.leapYearRule.previousLeapYear(yearNum);
+ if(lyYear !== null){
+ Logger.debug(`Resetting moon calculation first day to year: ${lyYear}`);
+ firstNewMoonDays = year.dateToDays(lyYear, this.firstNewMoon.month, this.firstNewMoon.day, true, true) - 1;
+ if(yearNum !== lyYear){
+ resetYearAdjustment += year.leapYearRule.fraction(yearNum);
+ }
+ }
+ } else if(this.firstNewMoon.yearReset === MoonYearResetOptions.XYears){
+ const resetMod = yearNum % this.firstNewMoon.yearX;
+ if(resetMod !== 0){
+ let resetYear = yearNum - resetMod;
+ firstNewMoonDays = year.dateToDays(resetYear, this.firstNewMoon.month, this.firstNewMoon.day, true, true) - 1;
+ resetYearAdjustment += resetMod / this.firstNewMoon.yearX;
+ }
+ }
+
+ const daysSoFar = year.dateToDays(yearNum, monthNum, dayNum, true, true) - 1;
+ const daysSinceReferenceMoon = daysSoFar - firstNewMoonDays + resetYearAdjustment;
+ const moonCycles = daysSinceReferenceMoon / this.cycleLength;
+ let daysIntoCycle = ((moonCycles - Math.floor(moonCycles)) * this.cycleLength) + this.cycleDayAdjust;
+
+ let phaseDays = 0;
+ let phase: MoonPhase | null = null;
+ for(let i = 0; i < this.phases.length; i++){
+ const newPhaseDays = phaseDays + this.phases[i].length;
+ if(daysIntoCycle >= phaseDays && daysIntoCycle < newPhaseDays){
+ phase = this.phases[i];
+ break;
+ }
+ phaseDays = newPhaseDays;
+ }
+ if(phase !== null){
+ return phase;
+ }
+ }
+ return this.phases[0];
+ }
+
+}
diff --git a/src/classes/season.test.ts b/src/classes/season.test.ts
new file mode 100644
index 00000000..5ccdd7d2
--- /dev/null
+++ b/src/classes/season.test.ts
@@ -0,0 +1,57 @@
+/**
+ * @jest-environment jsdom
+ */
+import "../../__mocks__/game";
+import "../../__mocks__/form-application";
+import "../../__mocks__/application";
+import "../../__mocks__/handlebars";
+import "../../__mocks__/event";
+import "../../__mocks__/crypto";
+
+import Season from "./season";
+import SimpleCalendar from "./simple-calendar";
+import Year from "./year";
+import Month from "./month";
+
+describe('Season Tests', () => {
+
+ let s: Season;
+
+ beforeEach(() => {
+ s = new Season('Spring', 1, 1);
+ });
+
+ test('Properties', () => {
+ expect(Object.keys(s).length).toBe(5); //Make sure no new properties have been added
+ expect(s.name).toBe('Spring');
+ expect(s.startingMonth).toBe(1);
+ expect(s.startingDay).toBe(1);
+ expect(s.color).toBe('#ffffff');
+ expect(s.customColor).toBe('');
+ });
+
+ test('Clone', () => {
+ expect(s.clone()).toStrictEqual(s);
+ });
+
+ test('To Template', () => {
+ const y = new Year(0);
+ let c = s.toTemplate(y);
+ expect(Object.keys(c).length).toBe(6); //Make sure no new properties have been added
+ expect(c.name).toBe('Spring');
+ expect(c.startingMonth).toBe(1);
+ expect(c.startingDay).toBe(1);
+ expect(c.color).toBe('#ffffff');
+ expect(c.customColor).toBe('');
+ expect(c.dayList).toStrictEqual([]);
+
+ SimpleCalendar.instance = new SimpleCalendar();
+
+ c = s.toTemplate(y);
+ expect(c.dayList.length).toStrictEqual(0);
+ y.months.push(new Month("Month 1", 1, 10));
+ c = s.toTemplate(y);
+ expect(c.dayList.length).toStrictEqual(10);
+ });
+
+});
diff --git a/src/classes/season.ts b/src/classes/season.ts
new file mode 100644
index 00000000..78b69946
--- /dev/null
+++ b/src/classes/season.ts
@@ -0,0 +1,78 @@
+import SimpleCalendar from "./simple-calendar";
+import {SeasonTemplate} from "../interfaces";
+import Year from "./year";
+
+/**
+ * All content around a season
+ */
+export default class Season {
+ /**
+ * The name of the season
+ * @type{string}
+ */
+ name: string;
+ /**
+ * The color of the season
+ * @type{string}
+ */
+ color: string = '#ffffff';
+ /**
+ * The custom color of the season
+ * @type{string}
+ */
+ customColor: string = '';
+ /**
+ * The month this season starts on
+ * @type{number}
+ */
+ startingMonth: number = -1;
+ /**
+ * The day of the starting month this season starts on
+ * @type{number}
+ */
+ startingDay: number = -1;
+
+ /**
+ * The Season Constructor
+ * @param {string} name The name of the season
+ * @param {number} startingMonth The month this season starts on
+ * @param {number} startingDay The day of the starting month this season starts on
+ */
+ constructor(name: string, startingMonth: number, startingDay: number) {
+ this.name = name;
+ this.startingMonth = startingMonth;
+ this.startingDay = startingDay;
+ }
+
+ /**
+ * Creates a clone of the current season
+ * @return {Season}
+ */
+ clone(): Season {
+ const t = new Season(this.name, this.startingMonth, this.startingDay);
+ t.color = this.color;
+ t.customColor = this.customColor;
+ return t;
+ }
+
+ /**
+ * Creates a template of the season used to render its information
+ * @param {Year} year The year to look in for the months and days list
+ */
+ toTemplate(year: Year){
+ const data: SeasonTemplate = {
+ name: this.name,
+ startingMonth: this.startingMonth,
+ startingDay: this.startingDay,
+ color: this.color,
+ customColor: this.customColor,
+ dayList: []
+ };
+
+ const month = year.months.find(m => m.numericRepresentation === data.startingMonth);
+ if(month){
+ data.dayList = month.days.map(d => d.toTemplate());
+ }
+ return data;
+ }
+}
diff --git a/src/classes/simple-calendar-configuration.test.ts b/src/classes/simple-calendar-configuration.test.ts
index 251df531..7115b270 100644
--- a/src/classes/simple-calendar-configuration.test.ts
+++ b/src/classes/simple-calendar-configuration.test.ts
@@ -15,7 +15,11 @@ import Mock = jest.Mock;
import Month from "./month";
import {Weekday} from "./weekday";
import {Logger} from "./logging";
-import {LeapYearRules} from "../constants";
+import {LeapYearRules, MoonIcons, MoonYearResetOptions} from "../constants";
+import Season from "./season";
+import Moon from "./moon";
+
+jest.mock('./importer');
describe('Simple Calendar Configuration Tests', () => {
@@ -32,6 +36,8 @@ describe('Simple Calendar Configuration Tests', () => {
y.months.push(new Month('T', 2, 15));
y.weekdays.push(new Weekday(1, 'S'));
y.weekdays.push(new Weekday(2, 'F'));
+ y.seasons.push(new Season('S', 5, 5));
+ y.moons.push(new Moon('Moon', 30));
y.selectedYear = 0;
y.visibleYear = 0;
y.months[0].current = true;
@@ -44,6 +50,7 @@ describe('Simple Calendar Configuration Tests', () => {
SimpleCalendarConfiguration.instance = new SimpleCalendarConfiguration(y);
//Spy on the inherited render function of the new instance
+ //@ts-ignore
renderSpy = jest.spyOn(SimpleCalendarConfiguration.instance, 'render');
//Clear all mock calls
(console.error).mockClear();
@@ -64,7 +71,7 @@ describe('Simple Calendar Configuration Tests', () => {
//@ts-ignore
expect(opts.resizable).toBe(true);
//@ts-ignore
- expect(opts.width).toBe(710);
+ expect(opts.width).toBe(960);
//@ts-ignore
expect(opts.height).toBe(700);
//@ts-ignore
@@ -100,6 +107,13 @@ describe('Simple Calendar Configuration Tests', () => {
expect(data.months).toStrictEqual(y.months.map(m => m.toTemplate()));
//@ts-ignore
expect(data.weekdays).toStrictEqual(y.weekdays.map(m => m.toTemplate()));
+
+ (game.modules.get).mockReturnValueOnce({active:true}).mockReturnValueOnce({active:true});
+ data = SimpleCalendarConfiguration.instance.getData();
+ //@ts-ignore
+ expect(data.importing.showCalendarWeather).toBe(true);
+ //@ts-ignore
+ expect(data.importing.showAboutTime).toBe(true);
});
test('Update Object', () => {
@@ -120,8 +134,8 @@ describe('Simple Calendar Configuration Tests', () => {
fakeQuery.length = 1;
//@ts-ignore
SimpleCalendarConfiguration.instance.activateListeners(fakeQuery);
- expect(fakeQuery.find).toHaveBeenCalledTimes(13);
- expect(onFunc).toHaveBeenCalledTimes(13);
+ expect(fakeQuery.find).toHaveBeenCalledTimes(26);
+ expect(onFunc).toHaveBeenCalledTimes(26);
});
test('Rebase Month Numbers', () => {
@@ -137,7 +151,7 @@ describe('Simple Calendar Configuration Tests', () => {
test('Add Month', () => {
const event = new Event('click');
const currentMonthLength = (SimpleCalendarConfiguration.instance.object).months.length;
- SimpleCalendarConfiguration.instance.addMonth(event);
+ SimpleCalendarConfiguration.instance.addToTable('month', event);
expect(renderSpy).toHaveBeenCalledTimes(1);
expect((SimpleCalendarConfiguration.instance.object).months.length).toBe(currentMonthLength + 1);
});
@@ -145,29 +159,29 @@ describe('Simple Calendar Configuration Tests', () => {
test('Remove Month', () => {
const event = new Event('click');
let currentMonthLength = (SimpleCalendarConfiguration.instance.object).months.length;
- SimpleCalendarConfiguration.instance.removeMonth(event);
+ SimpleCalendarConfiguration.instance.removeFromTable('month',event);
expect(renderSpy).not.toHaveBeenCalled();
expect((SimpleCalendarConfiguration.instance.object).months.length).toBe(currentMonthLength);
//Check for invalid index
(event.currentTarget).setAttribute('data-index', 'a');
currentMonthLength = (SimpleCalendarConfiguration.instance.object).months.length;
- SimpleCalendarConfiguration.instance.removeMonth(event);
+ SimpleCalendarConfiguration.instance.removeFromTable('month',event);
expect(renderSpy).not.toHaveBeenCalled();
expect((SimpleCalendarConfiguration.instance.object).months.length).toBe(currentMonthLength);
//Check for index outside of month length
(event.currentTarget).setAttribute('data-index', '12');
currentMonthLength = (SimpleCalendarConfiguration.instance.object).months.length;
- SimpleCalendarConfiguration.instance.removeMonth(event);
- expect(renderSpy).not.toHaveBeenCalled();
+ SimpleCalendarConfiguration.instance.removeFromTable('month',event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
expect((SimpleCalendarConfiguration.instance.object).months.length).toBe(currentMonthLength);
//Check for valid index
(event.currentTarget).setAttribute('data-index', '0');
currentMonthLength = (SimpleCalendarConfiguration.instance.object).months.length;
- SimpleCalendarConfiguration.instance.removeMonth(event);
- expect(renderSpy).toHaveBeenCalledTimes(1);
+ SimpleCalendarConfiguration.instance.removeFromTable('month',event);
+ expect(renderSpy).toHaveBeenCalledTimes(2);
expect((SimpleCalendarConfiguration.instance.object).months.length).toBe(currentMonthLength - 1);
expect((SimpleCalendarConfiguration.instance.object).months[0].name).toBe('T');
expect((SimpleCalendarConfiguration.instance.object).months[0].days.length).toBe(15);
@@ -175,15 +189,15 @@ describe('Simple Calendar Configuration Tests', () => {
//Check for removing all months
(event.currentTarget).setAttribute('data-index', 'all');
- SimpleCalendarConfiguration.instance.removeMonth(event);
- expect(renderSpy).toHaveBeenCalledTimes(2);
+ SimpleCalendarConfiguration.instance.removeFromTable('month',event);
+ expect(renderSpy).toHaveBeenCalledTimes(3);
expect((SimpleCalendarConfiguration.instance.object).months.length).toBe(0);
});
test('Add Weekday', () => {
const event = new Event('click');
const currentWeekdayLength = (SimpleCalendarConfiguration.instance.object).weekdays.length;
- SimpleCalendarConfiguration.instance.addWeekday(event);
+ SimpleCalendarConfiguration.instance.addToTable('weekday', event);
expect(renderSpy).toHaveBeenCalledTimes(1);
expect((SimpleCalendarConfiguration.instance.object).weekdays.length).toBe(currentWeekdayLength + 1);
});
@@ -191,44 +205,211 @@ describe('Simple Calendar Configuration Tests', () => {
test('Remove Weekday', () => {
const event = new Event('click');
let currentWeekdayLength = (SimpleCalendarConfiguration.instance.object).weekdays.length;
- SimpleCalendarConfiguration.instance.removeWeekday(event);
+ SimpleCalendarConfiguration.instance.removeFromTable('weekday',event);
expect(renderSpy).not.toHaveBeenCalled();
expect((SimpleCalendarConfiguration.instance.object).weekdays.length).toBe(currentWeekdayLength);
//Check for invalid index
(event.currentTarget).setAttribute('data-index', 'a');
currentWeekdayLength = (SimpleCalendarConfiguration.instance.object).weekdays.length;
- SimpleCalendarConfiguration.instance.removeWeekday(event);
+ SimpleCalendarConfiguration.instance.removeFromTable('weekday',event);
expect(renderSpy).not.toHaveBeenCalled();
expect((SimpleCalendarConfiguration.instance.object).weekdays.length).toBe(currentWeekdayLength);
//Check for index outside of weekday length
(event.currentTarget).setAttribute('data-index', '12');
currentWeekdayLength = (SimpleCalendarConfiguration.instance.object).weekdays.length;
- SimpleCalendarConfiguration.instance.removeWeekday(event);
- expect(renderSpy).not.toHaveBeenCalled();
+ SimpleCalendarConfiguration.instance.removeFromTable('weekday',event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
expect((SimpleCalendarConfiguration.instance.object).weekdays.length).toBe(currentWeekdayLength);
//Check for valid index
(event.currentTarget).setAttribute('data-index', '0');
currentWeekdayLength = (SimpleCalendarConfiguration.instance.object).weekdays.length;
- SimpleCalendarConfiguration.instance.removeWeekday(event);
- expect(renderSpy).toHaveBeenCalledTimes(1);
+ SimpleCalendarConfiguration.instance.removeFromTable('weekday',event);
+ expect(renderSpy).toHaveBeenCalledTimes(2);
expect((SimpleCalendarConfiguration.instance.object).weekdays.length).toBe(currentWeekdayLength - 1);
expect((SimpleCalendarConfiguration.instance.object).weekdays[0].name).toBe('F');
expect((SimpleCalendarConfiguration.instance.object).weekdays[0].numericRepresentation).toBe(1);
//Check for removing all weekdays
(event.currentTarget).setAttribute('data-index', 'all');
- SimpleCalendarConfiguration.instance.removeWeekday(event);
- expect(renderSpy).toHaveBeenCalledTimes(2);
+ SimpleCalendarConfiguration.instance.removeFromTable('weekday',event);
+ expect(renderSpy).toHaveBeenCalledTimes(3);
expect((SimpleCalendarConfiguration.instance.object).weekdays.length).toBe(0);
});
- test('Predefined Apply', () => {
- SimpleCalendarConfiguration.instance.predefinedApply(new Event('click'));
- //@ts-ignore
- expect(DialogRenderer).toHaveBeenCalled();
+ test('Add Season', () => {
+ const event = new Event('click');
+ const currentLength = (SimpleCalendarConfiguration.instance.object).seasons.length;
+ SimpleCalendarConfiguration.instance.addToTable('season', event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
+ expect((SimpleCalendarConfiguration.instance.object).seasons.length).toBe(currentLength + 1);
+ });
+
+ test('Remove Season', () => {
+ const event = new Event('click');
+ let currentLength = (SimpleCalendarConfiguration.instance.object).seasons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('season',event);
+ expect(renderSpy).not.toHaveBeenCalled();
+ expect((SimpleCalendarConfiguration.instance.object).seasons.length).toBe(currentLength);
+
+ //Check for invalid index
+ (event.currentTarget).setAttribute('data-index', 'a');
+ currentLength = (SimpleCalendarConfiguration.instance.object).seasons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('season',event);
+ expect(renderSpy).not.toHaveBeenCalled();
+ expect((SimpleCalendarConfiguration.instance.object).seasons.length).toBe(currentLength);
+
+ //Check for index outside of season length
+ (event.currentTarget).setAttribute('data-index', '12');
+ currentLength = (SimpleCalendarConfiguration.instance.object).seasons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('season',event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
+ expect((SimpleCalendarConfiguration.instance.object).seasons.length).toBe(currentLength);
+
+ //Check for valid index
+ (event.currentTarget).setAttribute('data-index', '0');
+ currentLength = (SimpleCalendarConfiguration.instance.object).seasons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('season',event);
+ expect(renderSpy).toHaveBeenCalledTimes(2);
+ expect((SimpleCalendarConfiguration.instance.object).seasons.length).toBe(currentLength - 1);
+
+ //Check for removing all seasons
+ (event.currentTarget).setAttribute('data-index', 'all');
+ SimpleCalendarConfiguration.instance.removeFromTable('season',event);
+ expect(renderSpy).toHaveBeenCalledTimes(3);
+ expect((SimpleCalendarConfiguration.instance.object).seasons.length).toBe(0);
+ });
+
+ test('Add Moon', () => {
+ const event = new Event('click');
+ const currentLength = (SimpleCalendarConfiguration.instance.object).moons.length;
+ SimpleCalendarConfiguration.instance.addToTable('moon', event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
+ expect((SimpleCalendarConfiguration.instance.object).moons.length).toBe(currentLength + 1);
+ });
+
+ test('Remove Moon', () => {
+ const event = new Event('click');
+ let currentLength = (SimpleCalendarConfiguration.instance.object).moons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon',event);
+ expect(renderSpy).not.toHaveBeenCalled();
+ expect((SimpleCalendarConfiguration.instance.object).moons.length).toBe(currentLength);
+
+ //Check for invalid index
+ (event.currentTarget).setAttribute('data-index', 'a');
+ currentLength = (SimpleCalendarConfiguration.instance.object).moons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon',event);
+ expect(renderSpy).not.toHaveBeenCalled();
+ expect((SimpleCalendarConfiguration.instance.object).moons.length).toBe(currentLength);
+
+ //Check for index outside of season length
+ (event.currentTarget).setAttribute('data-index', '12');
+ currentLength = (SimpleCalendarConfiguration.instance.object).moons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon',event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
+ expect((SimpleCalendarConfiguration.instance.object).moons.length).toBe(currentLength);
+
+ //Check for valid index
+ (event.currentTarget).setAttribute('data-index', '0');
+ currentLength = (SimpleCalendarConfiguration.instance.object).moons.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon',event);
+ expect(renderSpy).toHaveBeenCalledTimes(2);
+ expect((SimpleCalendarConfiguration.instance.object).moons.length).toBe(currentLength - 1);
+
+ //Check for removing all seasons
+ (event.currentTarget).setAttribute('data-index', 'all');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon',event);
+ expect(renderSpy).toHaveBeenCalledTimes(3);
+ expect((SimpleCalendarConfiguration.instance.object).moons.length).toBe(0);
+ });
+
+ test('Add Moon Phase', () => {
+ const event = new Event('click');
+ const currentLength = (SimpleCalendarConfiguration.instance.object).moons[0].phases.length;
+ SimpleCalendarConfiguration.instance.addToTable('moon-phase', event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ (event.currentTarget).setAttribute('data-moon-index', 'asd');
+ SimpleCalendarConfiguration.instance.addToTable('moon-phase', event);
+ expect(renderSpy).toHaveBeenCalledTimes(2);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ (event.currentTarget).setAttribute('data-moon-index', '0');
+ SimpleCalendarConfiguration.instance.addToTable('moon-phase', event);
+ expect(renderSpy).toHaveBeenCalledTimes(3);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength + 1);
+ });
+
+ test('Remove Moon Phase', () => {
+ const event = new Event('click');
+ let currentLength = (SimpleCalendarConfiguration.instance.object).moons[0].phases.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).not.toHaveBeenCalled();
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ //Check for invalid index
+ (event.currentTarget).setAttribute('data-index', 'a');
+ currentLength = (SimpleCalendarConfiguration.instance.object).moons[0].phases.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).not.toHaveBeenCalled();
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ //Check for index outside of season length
+ (event.currentTarget).setAttribute('data-index', '12');
+ currentLength = (SimpleCalendarConfiguration.instance.object).moons[0].phases.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(1);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ //Check for valid index
+ (event.currentTarget).setAttribute('data-index', '0');
+ currentLength = (SimpleCalendarConfiguration.instance.object).moons[0].phases.length;
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(2);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ //Check for invalid moon index
+ (event.currentTarget).setAttribute('data-moon-index', 'a');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(3);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ //Check for moon index outside of range
+ (event.currentTarget).setAttribute('data-moon-index', '12');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(4);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength);
+
+ //Check for valid moon index
+ (event.currentTarget).setAttribute('data-moon-index', '0');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(5);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(currentLength - 1);
+
+ //Check for removing all seasons
+ (event.currentTarget).setAttribute('data-index', 'all');
+ (event.currentTarget).removeAttribute('data-moon-index');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(6);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(0);
+
+ (event.currentTarget).setAttribute('data-moon-index', 'a');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(7);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(0);
+
+ (event.currentTarget).setAttribute('data-moon-index', '12');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(8);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(0);
+
+ (event.currentTarget).setAttribute('data-moon-index', '0');
+ SimpleCalendarConfiguration.instance.removeFromTable('moon-phase',event);
+ expect(renderSpy).toHaveBeenCalledTimes(9);
+ expect((SimpleCalendarConfiguration.instance.object).moons[0].phases.length).toBe(0);
});
test('Predefined Apply Confirm', () => {
@@ -297,16 +478,35 @@ describe('Simple Calendar Configuration Tests', () => {
const event = new Event('change');
(event.currentTarget).id = "scDefaultPlayerVisibility";
(event.currentTarget).checked = true;
+ (event.currentTarget).value = '';
//@ts-ignore
expect(SimpleCalendarConfiguration.instance.generalSettings.defaultPlayerNoteVisibility).toBe(false);
- SimpleCalendarConfiguration.instance.generalInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
//@ts-ignore
expect(SimpleCalendarConfiguration.instance.generalSettings.defaultPlayerNoteVisibility).toBe(true);
(event.currentTarget).id = "asd";
- SimpleCalendarConfiguration.instance.generalInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
//@ts-ignore
expect(SimpleCalendarConfiguration.instance.generalSettings.defaultPlayerNoteVisibility).toBe(true);
+
+ (event.currentTarget).id = "scGameWorldTime";
+ (event.currentTarget).value = 'self';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ //@ts-ignore
+ expect((SimpleCalendarConfiguration.instance.object).generalSettings.gameWorldTimeIntegration).toBe('self');
+
+ (event.currentTarget).id = "scShowClock";
+ (event.currentTarget).checked = true;
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ //@ts-ignore
+ expect((SimpleCalendarConfiguration.instance.object).generalSettings.showClock ).toBe(true);
+
+ (event.currentTarget).id = "scPlayersAddNotes";
+ (event.currentTarget).checked = true;
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ //@ts-ignore
+ expect((SimpleCalendarConfiguration.instance.object).generalSettings.playersAddNotes ).toBe(true);
});
test('Year Input Change', () => {
@@ -315,73 +515,115 @@ describe('Simple Calendar Configuration Tests', () => {
//Invalid current year
(event.currentTarget).value = 'asd';
const beforeYear = (SimpleCalendarConfiguration.instance.object).numericRepresentation;
- SimpleCalendarConfiguration.instance.yearInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).numericRepresentation).toBe(beforeYear);
//Valid current year
(event.currentTarget).value = '10';
- SimpleCalendarConfiguration.instance.yearInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).numericRepresentation).toBe(10);
//Prefix
(event.currentTarget).id = "scYearPreFix";
(event.currentTarget).value = 'Pre';
- SimpleCalendarConfiguration.instance.yearInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).prefix).toBe('Pre');
//Postfix
(event.currentTarget).id = "scYearPostFix";
(event.currentTarget).value = 'Post';
- SimpleCalendarConfiguration.instance.yearInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).postfix).toBe('Post');
//Invalid ID
(event.currentTarget).id = "asd";
- SimpleCalendarConfiguration.instance.yearInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).numericRepresentation).toBe(10);
expect((SimpleCalendarConfiguration.instance.object).prefix).toBe('Pre');
expect((SimpleCalendarConfiguration.instance.object).postfix).toBe('Post');
+
+ //Season Stuff
+ (event.currentTarget).id = "-asd";
+ (event.currentTarget).setAttribute('data-index', '0');
+ (event.currentTarget).setAttribute('class', 'season-name');
+ (event.currentTarget).value = 'Wint';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].name).toBe('Wint');
+
+ (event.currentTarget).setAttribute('class', 'season-custom');
+ (event.currentTarget).value = '#000000';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].customColor).toBe('#000000');
+ (event.currentTarget).value = '000000';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].customColor).toBe('#000000');
+
+ (event.currentTarget).setAttribute('class', 'season-month');
+ (event.currentTarget).value = '2';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].startingMonth).toBe(2);
+ (event.currentTarget).value = 'qwe';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].startingMonth).toBe(2);
+
+ (event.currentTarget).setAttribute('class', 'season-day');
+ (event.currentTarget).value = '2';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].startingDay).toBe(2);
+ (event.currentTarget).value = 'qwe';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].startingDay).toBe(2);
+
+ (event.currentTarget).setAttribute('class', 'season-color');
+ (event.currentTarget).value = '#fffeee';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].color).toBe('#fffeee');
+
+ (event.currentTarget).setAttribute('class', 'season-a');
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).seasons[0].color).toBe('#fffeee');
});
test('Month Input Change', () => {
const event = new Event('change');
+ (event.currentTarget).value = '';
(event.currentTarget).classList.remove('next');
//Test No Attributes
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect(console.debug).toHaveBeenCalledTimes(1);
//Test set index and no class or value
(event.currentTarget).setAttribute('data-index', 'a');
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect(console.debug).toHaveBeenCalledTimes(2);
//Test set index and class but no value
(event.currentTarget).classList.add('month-name');
- SimpleCalendarConfiguration.instance.monthInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(3);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(5);
//Test all attributes set but invalid index
(event.currentTarget).value = 'X';
- SimpleCalendarConfiguration.instance.monthInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(4);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(8);
//Test all attributes set but index outside of month length
(event.currentTarget).setAttribute('data-index', '12');
- SimpleCalendarConfiguration.instance.monthInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(5);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(11);
//Test all attributes for month name change
(event.currentTarget).setAttribute('data-index', '0');
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].name).toBe('X');
//Test all attributes for month day length change, invalid value
(event.currentTarget).classList.remove('month-name');
(event.currentTarget).classList.add('month-days');
let numDays = (SimpleCalendarConfiguration.instance.object).months[0].numberOfDays;
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].numberOfDays).toBe(numDays);
//Test all attributes for month day length change, set to same day length
(event.currentTarget).value = (SimpleCalendarConfiguration.instance.object).months[0].numberOfDays.toString();
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].numberOfDays).toBe(numDays);
//Test all attributes for month day length change
(event.currentTarget).value = '20';
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].numberOfDays).toBe(20);
//Test intercalary change
@@ -390,93 +632,290 @@ describe('Simple Calendar Configuration Tests', () => {
(event.currentTarget).classList.remove('month-days');
(event.currentTarget).classList.add('month-intercalary');
(event.currentTarget).checked = true;
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].intercalary).toBe(true);
(event.currentTarget).checked = false;
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].intercalary).toBe(false);
//Test intercalary include change
(event.currentTarget).classList.remove('month-intercalary');
(event.currentTarget).classList.add('month-intercalary-include');
(event.currentTarget).checked = true;
- SimpleCalendarConfiguration.instance.monthInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].intercalaryInclude).toBe(true);
//Test invlaid class name
(event.currentTarget).classList.remove('month-intercalary-include');
(event.currentTarget).classList.add('no');
- SimpleCalendarConfiguration.instance.monthInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(6);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(35);
});
test('Show Weekday Input Change', () => {
const event = new Event('change');
+ (event.currentTarget).id = 'scShowWeekdayHeaders';
+ (event.currentTarget).value = '';
(event.currentTarget).checked = true;
- SimpleCalendarConfiguration.instance.showWeekdayInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).showWeekdayHeadings).toBe(true);
(event.currentTarget).checked = false;
- SimpleCalendarConfiguration.instance.showWeekdayInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).showWeekdayHeadings).toBe(false);
});
test('Weekday Input Change', () => {
const event = new Event('change');
+ (event.currentTarget).classList.remove('next');
+ (event.currentTarget).classList.add('weekday-name');
+ (event.currentTarget).value = '';
//Test No Attributes
- SimpleCalendarConfiguration.instance.weekdayInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(1);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(2);
//Test set index and no value
(event.currentTarget).setAttribute('data-index', 'a');
- SimpleCalendarConfiguration.instance.weekdayInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(2);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(5);
//Test all attributes set but invalid index
(event.currentTarget).value = 'X';
- SimpleCalendarConfiguration.instance.weekdayInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(3);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(8);
//Test all attributes set but index outside of weekday length
(event.currentTarget).setAttribute('data-index', '12');
- SimpleCalendarConfiguration.instance.weekdayInputChange(event);
- expect(console.debug).toHaveBeenCalledTimes(4);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(11);
//Test all attributes for weekday name change
(event.currentTarget).setAttribute('data-index', '0');
- SimpleCalendarConfiguration.instance.weekdayInputChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).weekdays[0].name).toBe('X');
});
test('Leap Year Rule Change', () => {
const event = new Event('change');
- SimpleCalendarConfiguration.instance.leapYearRuleChange(event);
+ (event.currentTarget).id = 'scLeapYearRule';
+ (event.currentTarget).value = '';
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect(renderSpy).toHaveBeenCalledTimes(1);
});
test('Leap Year Month Change', () => {
const event = new Event('change');
+ (event.currentTarget).classList.remove('next');
+ (event.currentTarget).classList.add('month-leap-days');
+ (event.currentTarget).value = '';
//Test No Attributes
- SimpleCalendarConfiguration.instance.leapYearMonthChange(event);
- expect(console.debug).toHaveBeenCalledTimes(1);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(2);
//Test set index and no value
(event.currentTarget).setAttribute('data-index', 'a');
- SimpleCalendarConfiguration.instance.leapYearMonthChange(event);
- expect(console.debug).toHaveBeenCalledTimes(2);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(5);
//Test all attributes set but invalid index
(event.currentTarget).value = '4';
- SimpleCalendarConfiguration.instance.leapYearMonthChange(event);
- expect(console.debug).toHaveBeenCalledTimes(3);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(8);
//Test all attributes set but index outside of weekday length
(event.currentTarget).setAttribute('data-index', '12');
- SimpleCalendarConfiguration.instance.leapYearMonthChange(event);
- expect(console.debug).toHaveBeenCalledTimes(4);
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect(console.debug).toHaveBeenCalledTimes(11);
//Test all attributes for weekday name change
(event.currentTarget).setAttribute('data-index', '0');
- SimpleCalendarConfiguration.instance.leapYearMonthChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].numberOfLeapYearDays).toBe(4);
//Test invalid number of days
(event.currentTarget).value = 'asd';
- SimpleCalendarConfiguration.instance.leapYearMonthChange(event);
+ SimpleCalendarConfiguration.instance.inputChange(event);
expect((SimpleCalendarConfiguration.instance.object).months[0].numberOfLeapYearDays).toBe(4);
});
+ test('Time Input Change', () => {
+ const event = new Event('change');
+ (event.currentTarget).value = '';
+ (event.currentTarget).id = "asd";
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).time.hoursInDay).toBe(24);
+
+ //Invalid hours in day
+ (event.currentTarget).id = "scHoursInDay";
+ (event.currentTarget).value = 'asd';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((SimpleCalendarConfiguration.instance.object).time.hoursInDay).toBe(24);
+ //Valid hours in day
+ (event.currentTarget).value = '10';
+ SimpleCalendarConfiguration.instance.inputChange(event);
+ expect((