OctoPrint-Wrapped 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. octoprint_wrapped-1.0.0/MANIFEST.in +4 -0
  2. octoprint_wrapped-1.0.0/OctoPrint_Wrapped.egg-info/PKG-INFO +45 -0
  3. octoprint_wrapped-1.0.0/OctoPrint_Wrapped.egg-info/SOURCES.txt +21 -0
  4. octoprint_wrapped-1.0.0/OctoPrint_Wrapped.egg-info/dependency_links.txt +1 -0
  5. octoprint_wrapped-1.0.0/OctoPrint_Wrapped.egg-info/entry_points.txt +2 -0
  6. octoprint_wrapped-1.0.0/OctoPrint_Wrapped.egg-info/requires.txt +3 -0
  7. octoprint_wrapped-1.0.0/OctoPrint_Wrapped.egg-info/top_level.txt +1 -0
  8. octoprint_wrapped-1.0.0/PKG-INFO +45 -0
  9. octoprint_wrapped-1.0.0/README.md +32 -0
  10. octoprint_wrapped-1.0.0/octoprint_wrapped/__init__.py +257 -0
  11. octoprint_wrapped-1.0.0/octoprint_wrapped/static/clientjs/wrapped.js +27 -0
  12. octoprint_wrapped-1.0.0/octoprint_wrapped/static/js/ko.src.svgtopng.js +36 -0
  13. octoprint_wrapped-1.0.0/octoprint_wrapped/static/js/wrapped.js +178 -0
  14. octoprint_wrapped-1.0.0/octoprint_wrapped/static/pure-snow/LICENSE +21 -0
  15. octoprint_wrapped-1.0.0/octoprint_wrapped/static/pure-snow/pure-snow.css +10 -0
  16. octoprint_wrapped-1.0.0/octoprint_wrapped/static/pure-snow/pure-snow.js +146 -0
  17. octoprint_wrapped-1.0.0/octoprint_wrapped/templates/wrapped.svg.jinja2 +1258 -0
  18. octoprint_wrapped-1.0.0/octoprint_wrapped/templates/wrapped_about.jinja2 +24 -0
  19. octoprint_wrapped-1.0.0/octoprint_wrapped/templates/wrapped_navbar_snowfall.jinja2 +7 -0
  20. octoprint_wrapped-1.0.0/octoprint_wrapped/templates/wrapped_navbar_wrapped.jinja2 +7 -0
  21. octoprint_wrapped-1.0.0/pyproject.toml +138 -0
  22. octoprint_wrapped-1.0.0/setup.cfg +4 -0
  23. octoprint_wrapped-1.0.0/setup.py +4 -0
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ recursive-include octoprint_wrapped/templates *
3
+ recursive-include octoprint_wrapped/translations *
4
+ recursive-include octoprint_wrapped/static *
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: OctoPrint-Wrapped
3
+ Version: 1.0.0
4
+ Summary: Get your yearly OctoPrint stats and let it snow! Depends on the Achievements plugin.
5
+ Author-email: Gina Häußge <gina@octoprint.org>
6
+ License: AGPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/OctoPrint/OctoPrint-Wrapped
8
+ Requires-Python: <4,>=3.9
9
+ Description-Content-Type: text/markdown
10
+ Provides-Extra: develop
11
+ Requires-Dist: go-task-bin; extra == "develop"
12
+ Dynamic: license
13
+
14
+ # OctoPrint Wrapped! 🎁
15
+
16
+ Get your yearly OctoPrint stats a shareable "wrapped" picture - and let it snow! ❄️
17
+
18
+ <img src="https://raw.githubusercontent.com/OctoPrint/OctoPrint-Wrapped/main/extras/wrapped-demo.png" width="400" height="400" alt="Demo OctoPrint Wrapped share picture" />
19
+
20
+ ![Demo of snow effect](https://raw.githubusercontent.com/OctoPrint/OctoPrint-Wrapped/main/extras/snowfall-demo.gif)
21
+
22
+ The stats picture depends on the Achievements plugin being enabled (as it takes care of
23
+ the stats collection during the year) and can be opened via the little gift package icon
24
+ in the navbar.
25
+
26
+ The snow effect can always be toggled during the season using the little snowflake icon
27
+ in the navbar, and its setting persists through the browser's local storage.
28
+
29
+ Both wrapped and snowfall are only available from December 1st until January 10th.
30
+
31
+ ## Setup
32
+
33
+ Install via the bundled [Plugin Manager](https://docs.octoprint.org/en/main/bundledplugins/pluginmanager.html)
34
+ or manually using this URL:
35
+
36
+ https://github.com/OctoPrint/OctoPrint-Wrapped/archive/main.zip
37
+
38
+ ## Configuration
39
+
40
+ The plugin does not have any configuration options.
41
+
42
+ ## Acknowledgements
43
+
44
+ The snowfall is implemented with a slightly customized version of [pure-snow.js](https://github.com/hyperstown/pure-snow.js),
45
+ released under the MIT license and bundled here under `octoprint_wrapped/static/pure-snow`.
@@ -0,0 +1,21 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ OctoPrint_Wrapped.egg-info/PKG-INFO
6
+ OctoPrint_Wrapped.egg-info/SOURCES.txt
7
+ OctoPrint_Wrapped.egg-info/dependency_links.txt
8
+ OctoPrint_Wrapped.egg-info/entry_points.txt
9
+ OctoPrint_Wrapped.egg-info/requires.txt
10
+ OctoPrint_Wrapped.egg-info/top_level.txt
11
+ octoprint_wrapped/__init__.py
12
+ octoprint_wrapped/static/clientjs/wrapped.js
13
+ octoprint_wrapped/static/js/ko.src.svgtopng.js
14
+ octoprint_wrapped/static/js/wrapped.js
15
+ octoprint_wrapped/static/pure-snow/LICENSE
16
+ octoprint_wrapped/static/pure-snow/pure-snow.css
17
+ octoprint_wrapped/static/pure-snow/pure-snow.js
18
+ octoprint_wrapped/templates/wrapped.svg.jinja2
19
+ octoprint_wrapped/templates/wrapped_about.jinja2
20
+ octoprint_wrapped/templates/wrapped_navbar_snowfall.jinja2
21
+ octoprint_wrapped/templates/wrapped_navbar_wrapped.jinja2
@@ -0,0 +1,2 @@
1
+ [octoprint.plugin]
2
+ wrapped = octoprint_wrapped
@@ -0,0 +1,3 @@
1
+
2
+ [develop]
3
+ go-task-bin
@@ -0,0 +1 @@
1
+ octoprint_wrapped
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: OctoPrint-Wrapped
3
+ Version: 1.0.0
4
+ Summary: Get your yearly OctoPrint stats and let it snow! Depends on the Achievements plugin.
5
+ Author-email: Gina Häußge <gina@octoprint.org>
6
+ License: AGPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/OctoPrint/OctoPrint-Wrapped
8
+ Requires-Python: <4,>=3.9
9
+ Description-Content-Type: text/markdown
10
+ Provides-Extra: develop
11
+ Requires-Dist: go-task-bin; extra == "develop"
12
+ Dynamic: license
13
+
14
+ # OctoPrint Wrapped! 🎁
15
+
16
+ Get your yearly OctoPrint stats a shareable "wrapped" picture - and let it snow! ❄️
17
+
18
+ <img src="https://raw.githubusercontent.com/OctoPrint/OctoPrint-Wrapped/main/extras/wrapped-demo.png" width="400" height="400" alt="Demo OctoPrint Wrapped share picture" />
19
+
20
+ ![Demo of snow effect](https://raw.githubusercontent.com/OctoPrint/OctoPrint-Wrapped/main/extras/snowfall-demo.gif)
21
+
22
+ The stats picture depends on the Achievements plugin being enabled (as it takes care of
23
+ the stats collection during the year) and can be opened via the little gift package icon
24
+ in the navbar.
25
+
26
+ The snow effect can always be toggled during the season using the little snowflake icon
27
+ in the navbar, and its setting persists through the browser's local storage.
28
+
29
+ Both wrapped and snowfall are only available from December 1st until January 10th.
30
+
31
+ ## Setup
32
+
33
+ Install via the bundled [Plugin Manager](https://docs.octoprint.org/en/main/bundledplugins/pluginmanager.html)
34
+ or manually using this URL:
35
+
36
+ https://github.com/OctoPrint/OctoPrint-Wrapped/archive/main.zip
37
+
38
+ ## Configuration
39
+
40
+ The plugin does not have any configuration options.
41
+
42
+ ## Acknowledgements
43
+
44
+ The snowfall is implemented with a slightly customized version of [pure-snow.js](https://github.com/hyperstown/pure-snow.js),
45
+ released under the MIT license and bundled here under `octoprint_wrapped/static/pure-snow`.
@@ -0,0 +1,32 @@
1
+ # OctoPrint Wrapped! 🎁
2
+
3
+ Get your yearly OctoPrint stats a shareable "wrapped" picture - and let it snow! ❄️
4
+
5
+ <img src="https://raw.githubusercontent.com/OctoPrint/OctoPrint-Wrapped/main/extras/wrapped-demo.png" width="400" height="400" alt="Demo OctoPrint Wrapped share picture" />
6
+
7
+ ![Demo of snow effect](https://raw.githubusercontent.com/OctoPrint/OctoPrint-Wrapped/main/extras/snowfall-demo.gif)
8
+
9
+ The stats picture depends on the Achievements plugin being enabled (as it takes care of
10
+ the stats collection during the year) and can be opened via the little gift package icon
11
+ in the navbar.
12
+
13
+ The snow effect can always be toggled during the season using the little snowflake icon
14
+ in the navbar, and its setting persists through the browser's local storage.
15
+
16
+ Both wrapped and snowfall are only available from December 1st until January 10th.
17
+
18
+ ## Setup
19
+
20
+ Install via the bundled [Plugin Manager](https://docs.octoprint.org/en/main/bundledplugins/pluginmanager.html)
21
+ or manually using this URL:
22
+
23
+ https://github.com/OctoPrint/OctoPrint-Wrapped/archive/main.zip
24
+
25
+ ## Configuration
26
+
27
+ The plugin does not have any configuration options.
28
+
29
+ ## Acknowledgements
30
+
31
+ The snowfall is implemented with a slightly customized version of [pure-snow.js](https://github.com/hyperstown/pure-snow.js),
32
+ released under the MIT license and bundled here under `octoprint_wrapped/static/pure-snow`.
@@ -0,0 +1,257 @@
1
+ import json
2
+ import os
3
+ from typing import Optional
4
+
5
+ import flask
6
+ import octoprint.plugin
7
+ from flask_babel import gettext
8
+ from octoprint.access.permissions import Permissions
9
+ from octoprint.schema import BaseModel
10
+
11
+ WEEKDAYS = [
12
+ "Monday",
13
+ "Tuesday",
14
+ "Wednesday",
15
+ "Thursday",
16
+ "Friday",
17
+ "Saturday",
18
+ "Sunday",
19
+ ]
20
+ SECONDS_MINUTE = 60
21
+ SECONDS_HOUR = SECONDS_MINUTE * 60
22
+ SECONDS_DAY = SECONDS_HOUR * 24
23
+
24
+
25
+ class YearStats(BaseModel):
26
+ year: int
27
+ prints_completed: int
28
+ total_print_duration: str
29
+ longest_print: str
30
+ busiest_weekday: str
31
+ files_uploaded: int
32
+ octoprint_versions: int
33
+
34
+
35
+ class ApiResponse(BaseModel):
36
+ years: list[int]
37
+
38
+
39
+ class WrappedPlugin(
40
+ octoprint.plugin.AssetPlugin,
41
+ octoprint.plugin.BlueprintPlugin,
42
+ octoprint.plugin.SimpleApiPlugin,
43
+ octoprint.plugin.TemplatePlugin,
44
+ ):
45
+ ##~~ AssetPlugin mixin
46
+
47
+ def get_assets(self):
48
+ return {
49
+ "clientjs": ["clientjs/wrapped.js"],
50
+ "js": ["js/wrapped.js", "js/ko.src.svgtopng.js"],
51
+ }
52
+
53
+ ##~~ BlueprintPlugin mixin
54
+
55
+ def is_blueprint_csrf_protected(self):
56
+ return True
57
+
58
+ def is_protected(self):
59
+ return True
60
+
61
+ @octoprint.plugin.BlueprintPlugin.route("/<int:year>.svg", methods=["GET"])
62
+ def get_svg(self, year):
63
+ if (
64
+ not hasattr(Permissions, "PLUGIN_ACHIEVEMENTS_VIEW")
65
+ or not Permissions.PLUGIN_ACHIEVEMENTS_VIEW.can()
66
+ ):
67
+ flask.abort(403)
68
+
69
+ stats = self._get_year_stats(year)
70
+ if stats is None:
71
+ flask.abort(404)
72
+
73
+ response = flask.make_response(
74
+ flask.render_template("wrapped.svg.jinja2", **stats.model_dump(by_alias=True))
75
+ )
76
+ response.headers["Content-Type"] = "image/svg+xml"
77
+ return response
78
+
79
+ ##~~ SimpleApiPlugin mixin
80
+
81
+ def is_api_protected(self):
82
+ return True
83
+
84
+ def on_api_get(self, request):
85
+ if (
86
+ not hasattr(Permissions, "PLUGIN_ACHIEVEMENTS_VIEW")
87
+ or not Permissions.PLUGIN_ACHIEVEMENTS_VIEW.can()
88
+ ):
89
+ flask.abort(403)
90
+
91
+ response = ApiResponse(years=self._get_available_years())
92
+ return flask.jsonify(response.model_dump(by_alias=True))
93
+
94
+ ##~~ Softwareupdate hook
95
+
96
+ def get_update_information(self):
97
+ return {
98
+ "wrapped": {
99
+ "displayName": "OctoPrint Wrapped!",
100
+ "displayVersion": self._plugin_version,
101
+ # version check: github repository
102
+ "type": "github_release",
103
+ "user": "OctoPrint",
104
+ "repo": "OctoPrint-Wrapped",
105
+ "current": self._plugin_version,
106
+ # update method: pip
107
+ "pip": "https://github.com/OctoPrint/OctoPrint-Wrapped/archive/{target_version}.zip",
108
+ }
109
+ }
110
+
111
+ ##~~ TemplatePlugin mixin
112
+
113
+ def is_template_autoescaped(self):
114
+ return True
115
+
116
+ def get_template_configs(self):
117
+ return [
118
+ {
119
+ "type": "about",
120
+ "name": gettext("OctoPrint Wrapped!"),
121
+ "template": "wrapped_about.jinja2",
122
+ "custom_bindings": True,
123
+ },
124
+ {
125
+ "type": "navbar",
126
+ "template": "wrapped_navbar_wrapped.jinja2",
127
+ "custom_bindings": True,
128
+ },
129
+ {
130
+ "type": "navbar",
131
+ "template": "wrapped_navbar_snowfall.jinja2",
132
+ "custom_bindings": True,
133
+ },
134
+ ]
135
+
136
+ ##~~ helpers
137
+
138
+ def _get_year_stats_folder(self) -> Optional[str]:
139
+ folder = os.path.join(self.get_plugin_data_folder(), "..", "achievements")
140
+ if not os.path.isdir(folder):
141
+ return None
142
+ return folder
143
+
144
+ def _get_year_stats_file(self, year: int) -> Optional[str]:
145
+ folder = self._get_year_stats_folder()
146
+ if not folder:
147
+ return None
148
+
149
+ year_path = os.path.join(folder, f"{year}.json")
150
+ if not os.path.isfile(year_path):
151
+ return None
152
+
153
+ return year_path
154
+
155
+ def _get_available_years(self) -> list[int]:
156
+ import re
157
+
158
+ stats_folder = self._get_year_stats_folder()
159
+ if not stats_folder:
160
+ return []
161
+
162
+ pattern = re.compile(r"\d{4}.json")
163
+
164
+ years = []
165
+ for entry in os.scandir(stats_folder):
166
+ if not entry.is_file():
167
+ continue
168
+
169
+ if pattern.fullmatch(entry.name):
170
+ year, _ = os.path.splitext(entry.name)
171
+ years.append(int(year))
172
+
173
+ return years
174
+
175
+ def _get_year_stats(self, year: int) -> Optional[YearStats]:
176
+ stats_file = self._get_year_stats_file(year)
177
+ if not stats_file:
178
+ return None
179
+
180
+ try:
181
+ with open(stats_file) as f:
182
+ stats = json.load(f)
183
+ except Exception:
184
+ self._logger.exception(
185
+ f"Error while reading yearly stats for {year} from {stats_file}"
186
+ )
187
+ return None
188
+
189
+ try:
190
+ weekday_stats = stats.get("prints_started_per_weekday", {})
191
+ busiest = None
192
+ for key, value in weekday_stats.items():
193
+ if busiest is None or value > busiest[1]:
194
+ busiest = (key, value)
195
+
196
+ if busiest:
197
+ weekday = WEEKDAYS[int(busiest[0])]
198
+ else:
199
+ weekday = "-"
200
+
201
+ return YearStats(
202
+ year=year,
203
+ prints_completed=stats.get("prints_finished", 0),
204
+ total_print_duration=self._to_duration_days(
205
+ int(stats.get("print_duration_total", 0))
206
+ ),
207
+ longest_print=self._to_duration_hours(
208
+ int(stats.get("longest_print_duration", 0))
209
+ ),
210
+ busiest_weekday=weekday,
211
+ files_uploaded=int(stats.get("files_uploaded", 0)),
212
+ octoprint_versions=int(stats.get("seen_versions", 1)),
213
+ )
214
+ except Exception:
215
+ self._logger.exception(
216
+ f"Error while parsing yearly stats for {year} from {stats_file}"
217
+ )
218
+ return None
219
+
220
+ def _to_duration_days(self, seconds: int) -> str:
221
+ days = int(seconds / SECONDS_DAY)
222
+ seconds -= days * SECONDS_DAY
223
+
224
+ hours = int(seconds / SECONDS_HOUR)
225
+ seconds -= hours * SECONDS_HOUR
226
+
227
+ minutes = int(seconds / SECONDS_MINUTE)
228
+ seconds -= minutes * SECONDS_MINUTE
229
+
230
+ if days >= 100:
231
+ # strip the minutes to keep things fitting...
232
+ return f"{days}d {hours}h"
233
+ else:
234
+ return f"{days}d {hours}h {minutes}m"
235
+
236
+ def _to_duration_hours(self, seconds: int) -> str:
237
+ hours = int(seconds / SECONDS_HOUR)
238
+ seconds -= hours * SECONDS_HOUR
239
+
240
+ minutes = int(seconds / SECONDS_MINUTE)
241
+ seconds -= minutes * SECONDS_MINUTE
242
+
243
+ return f"{hours}h {minutes}m"
244
+
245
+
246
+ __plugin_name__ = "OctoPrint Wrapped!"
247
+ __plugin_pythoncompat__ = ">=3.9,<4" # Only Python 3
248
+
249
+
250
+ def __plugin_load__():
251
+ global __plugin_implementation__
252
+ __plugin_implementation__ = WrappedPlugin()
253
+
254
+ global __plugin_hooks__
255
+ __plugin_hooks__ = {
256
+ "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information,
257
+ }
@@ -0,0 +1,27 @@
1
+ (function (global, factory) {
2
+ if (typeof define === "function" && define.amd) {
3
+ define(["OctoPrintClient"], factory);
4
+ } else {
5
+ factory(global.OctoPrintClient);
6
+ }
7
+ })(this, function (OctoPrintClient) {
8
+ var OctoPrintWrappedClient = function (base) {
9
+ this.base = base;
10
+
11
+ this.baseUrl = this.base.getBlueprintUrl("wrapped");
12
+ this.apiUrl = this.base.getSimpleApiUrl("wrapped");
13
+ };
14
+
15
+ OctoPrintWrappedClient.prototype.get = function (opts) {
16
+ return this.base.get(this.apiUrl, opts);
17
+ };
18
+
19
+ OctoPrintWrappedClient.prototype.getYearSvgUrl = function (year, opts) {
20
+ return this.baseUrl + year + ".svg";
21
+ };
22
+
23
+ // register plugin component
24
+ OctoPrintClient.registerPluginComponent("wrapped", OctoPrintWrappedClient);
25
+
26
+ return OctoPrintWrappedClient;
27
+ });
@@ -0,0 +1,36 @@
1
+ ko.bindingHandlers["src.svgtopng"] = {
2
+ init: (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) => {
3
+ const value = valueAccessor();
4
+ const valueUnwrapped = ko.unwrap(value);
5
+ if (!valueUnwrapped) return;
6
+
7
+ const $element = $(element);
8
+
9
+ $.get(valueUnwrapped).done((data, textStatus, xhr) => {
10
+ const svgString = xhr.responseText;
11
+
12
+ const svg = new Blob([svgString], {
13
+ type: "image/svg+xml;charset=utf-8"
14
+ });
15
+
16
+ const url = URL.createObjectURL(svg);
17
+
18
+ const img = new Image();
19
+ img.onload = () => {
20
+ URL.revokeObjectURL(url);
21
+
22
+ const canvas = document.createElement("canvas");
23
+ canvas.width = img.width;
24
+ canvas.height = img.height;
25
+
26
+ const ctx = canvas.getContext("2d");
27
+ ctx.drawImage(img, 0, 0);
28
+
29
+ const png = canvas.toDataURL("image/png");
30
+ $element.attr("src", png);
31
+ };
32
+ img.src = url;
33
+ });
34
+ }
35
+ };
36
+ ko.bindingHandlers["src.svgtopng"].update = ko.bindingHandlers["src.svgtopng"].init;
@@ -0,0 +1,178 @@
1
+ /*
2
+ * View model for OctoPrint-Wrapped
3
+ *
4
+ * Author: Gina Häußge
5
+ * License: AGPL-3.0-or-later
6
+ */
7
+ $(function () {
8
+ const FLAKES = 50;
9
+ const MIN_DURATION = 5;
10
+ const MAX_DURATION = 15;
11
+
12
+ const MONTH_DECEMBER = 11;
13
+ const MONTH_JANUARY = 0;
14
+
15
+ const SNOWFALL_LOCAL_STORAGE_KEY = "plugin.wrapped.snowfall";
16
+ const snowfallToLocalStorage = (value) => {
17
+ saveToLocalStorage(SNOWFALL_LOCAL_STORAGE_KEY, {
18
+ enabled: value
19
+ });
20
+ };
21
+
22
+ const snowfallFromLocalStorage = () => {
23
+ const data = loadFromLocalStorage(SNOWFALL_LOCAL_STORAGE_KEY);
24
+ if (data["enabled"] !== undefined) return !!data["enabled"];
25
+ return false;
26
+ };
27
+
28
+ function WrappedViewModel(parameters) {
29
+ const self = this;
30
+
31
+ self.loginState = parameters[0];
32
+ self.access = parameters[1];
33
+ self.aboutVM = parameters[2];
34
+
35
+ self.availableYears = ko.observableArray([]);
36
+
37
+ self.snowfallEnabled = false;
38
+ self.snowfallContainer = undefined;
39
+
40
+ self.currentWrapped = ko.pureComputed(() => {
41
+ if (!self.withinWrappedSeason()) return false;
42
+
43
+ const now = new Date();
44
+ const year =
45
+ now.getMonth() == MONTH_DECEMBER
46
+ ? now.getFullYear() // still in December
47
+ : now.getFullYear() - 1; // already January
48
+ const years = self.availableYears();
49
+
50
+ if (years.indexOf(year) !== -1 && self.withinWrappedSeason()) {
51
+ return year;
52
+ } else {
53
+ return false;
54
+ }
55
+ });
56
+
57
+ self.currentWrappedAvailable = ko.pureComputed(() => {
58
+ return self.currentWrapped() !== false;
59
+ });
60
+
61
+ self.currentSvgUrl = ko.pureComputed(() => {
62
+ const year = self.currentWrapped();
63
+ if (!year) return false;
64
+
65
+ return OctoPrint.plugins.wrapped.getYearSvgUrl(year);
66
+ });
67
+
68
+ self.withinWrappedSeason = ko.pureComputed(() => {
69
+ // wrapped Season = Dec 1st until January 10th
70
+ const now = new Date();
71
+ return (
72
+ now.getMonth() == MONTH_DECEMBER ||
73
+ (now.getMonth() == MONTH_JANUARY && now.getDate() < 10)
74
+ );
75
+ });
76
+
77
+ self.requestData = () => {
78
+ if (
79
+ !self.loginState.hasPermission(
80
+ self.access.permissions.PLUGIN_ACHIEVEMENTS_VIEW
81
+ )
82
+ ) {
83
+ return;
84
+ }
85
+ OctoPrint.plugins.wrapped.get().done(self.fromResponse);
86
+ };
87
+
88
+ self.fromResponse = (response) => {
89
+ self.availableYears(response.years);
90
+ };
91
+
92
+ self.showWrapped = () => {
93
+ self.aboutVM.show("about_plugin_wrapped");
94
+ return false;
95
+ };
96
+
97
+ self.toggleSnowfall = () => {
98
+ self.snowfallEnabled = !self.snowfallEnabled;
99
+ snowfallToLocalStorage(self.snowfallEnabled);
100
+ self.updateSnowfall();
101
+ };
102
+
103
+ self.updateSnowfall = () => {
104
+ let container = document.getElementById("snow");
105
+
106
+ const body = document.getElementsByTagName("body")[0];
107
+ const head = document.getElementsByTagName("head")[0];
108
+
109
+ if (!self.snowfallEnabled) {
110
+ if (typeof showSnow !== "undefined") showSnow(false);
111
+ } else if (self.snowfallEnabled) {
112
+ if (!self.withinWrappedSeason()) return;
113
+
114
+ if (!container) {
115
+ container = document.createElement("div");
116
+ container.id = "snow";
117
+ container.dataset.count = FLAKES;
118
+ container.dataset.durmin = MIN_DURATION;
119
+ container.dataset.durmax = MAX_DURATION;
120
+ body.insertBefore(container, body.firstChild);
121
+
122
+ const styleSnow = document.createElement("link");
123
+ styleSnow.href =
124
+ BASEURL + "plugin/wrapped/static/pure-snow/pure-snow.css";
125
+ styleSnow.rel = "stylesheet";
126
+ head.appendChild(styleSnow);
127
+
128
+ const scriptSnow = document.createElement("script");
129
+ scriptSnow.src =
130
+ BASEURL + "plugin/wrapped/static/pure-snow/pure-snow.js";
131
+ scriptSnow.defer = true;
132
+ scriptSnow.onload = () => {
133
+ setTimeout(() => {
134
+ if (
135
+ typeof createSnow === "undefined" ||
136
+ typeof showSnow === "undefined"
137
+ )
138
+ return;
139
+ createSnow();
140
+ showSnow(true);
141
+ }, 500);
142
+ };
143
+ head.appendChild(scriptSnow);
144
+ } else {
145
+ if (typeof showSnow !== "undefined") showSnow(true);
146
+ }
147
+ }
148
+ };
149
+
150
+ self.onUserPermissionsChanged =
151
+ self.onUserLoggedIn =
152
+ self.onUserLoggedOut =
153
+ (user) => {
154
+ if (
155
+ self.loginState.hasPermission(
156
+ self.access.permissions.PLUGIN_ACHIEVEMENTS_VIEW
157
+ )
158
+ ) {
159
+ self.requestData();
160
+ }
161
+ };
162
+
163
+ self.onStartup = () => {
164
+ self.snowfallEnabled = snowfallFromLocalStorage();
165
+ self.updateSnowfall();
166
+ };
167
+ }
168
+
169
+ OCTOPRINT_VIEWMODELS.push({
170
+ construct: WrappedViewModel,
171
+ dependencies: ["loginStateViewModel", "accessViewModel", "aboutViewModel"],
172
+ elements: [
173
+ "#about_plugin_wrapped",
174
+ "#navbar_plugin_wrapped",
175
+ "#navbar_plugin_wrapped_2"
176
+ ]
177
+ });
178
+ });
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020-present, hyperstown (github.com/hyperstown/)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.