@cocreate/pwa 1.20.0 → 1.22.0
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.
- package/CHANGELOG.md +14 -0
- package/demo/geolocation.html +73 -0
- package/package.json +1 -1
- package/src/geolocation.js +92 -0
- package/src/index.js +107 -96
- package/src/theme.js +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.22.0](https://github.com/CoCreate-app/CoCreate-pwa/compare/v1.21.0...v1.22.0) (2025-01-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* geolocation intergration ([4bc95fb](https://github.com/CoCreate-app/CoCreate-pwa/commit/4bc95fb1192efec090da1a45698f347376427d44))
|
|
7
|
+
|
|
8
|
+
# [1.21.0](https://github.com/CoCreate-app/CoCreate-pwa/compare/v1.20.0...v1.21.0) (2024-12-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* dynamic theme handling ([c2688f2](https://github.com/CoCreate-app/CoCreate-pwa/commit/c2688f2f148f0c68e3693cf932a24976658eb475))
|
|
14
|
+
|
|
1
15
|
# [1.20.0](https://github.com/CoCreate-app/CoCreate-pwa/compare/v1.19.4...v1.20.0) (2024-11-04)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Geolocation Web API Demo</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<form id="locationForm">
|
|
10
|
+
<input
|
|
11
|
+
geolocation="getCurrentPosition"
|
|
12
|
+
geolocation-key="coords.latitude"
|
|
13
|
+
placeholder="Latitude" />
|
|
14
|
+
<input
|
|
15
|
+
geolocation="getCurrentPosition"
|
|
16
|
+
geolocation-key="coords.longitude"
|
|
17
|
+
placeholder="Longitude" />
|
|
18
|
+
<input
|
|
19
|
+
geolocation="getCurrentPosition"
|
|
20
|
+
geolocation-key="coords.accuracy"
|
|
21
|
+
placeholder="Accuracy (meters)" />
|
|
22
|
+
<button actions="geoLocation.getCurrentPosition">
|
|
23
|
+
Get Location
|
|
24
|
+
</button>
|
|
25
|
+
</form>
|
|
26
|
+
|
|
27
|
+
<form id="trackLocationForm">
|
|
28
|
+
<input
|
|
29
|
+
geolocation="watchPosition"
|
|
30
|
+
geolocation-key="id"
|
|
31
|
+
placeholder="id"
|
|
32
|
+
input-next
|
|
33
|
+
input-attribute="value" />
|
|
34
|
+
<input
|
|
35
|
+
geolocation="clearWatch"
|
|
36
|
+
geolocation-key="id"
|
|
37
|
+
placeholder="id" />
|
|
38
|
+
<input
|
|
39
|
+
geolocation="watchPosition"
|
|
40
|
+
geolocation-key="coords.latitude"
|
|
41
|
+
placeholder="Latitude" />
|
|
42
|
+
<input
|
|
43
|
+
geolocation="watchPosition"
|
|
44
|
+
geolocation-key="coords.longitude"
|
|
45
|
+
placeholder="Longitude" />
|
|
46
|
+
<input
|
|
47
|
+
geolocation="watchPosition"
|
|
48
|
+
geolocation-key="coords.speed"
|
|
49
|
+
placeholder="Speed (m/s)" />
|
|
50
|
+
<button actions="geoLocation.watchPosition">Start Tracking</button>
|
|
51
|
+
<button actions="geoLocation.clearWatch">Stop Tracking</button>
|
|
52
|
+
</form>
|
|
53
|
+
|
|
54
|
+
<form id="errorForm">
|
|
55
|
+
<input
|
|
56
|
+
geolocation="getCurrentPosition"
|
|
57
|
+
geolocation-key="coords.latitude"
|
|
58
|
+
placeholder="Latitude" />
|
|
59
|
+
<input
|
|
60
|
+
geolocation="getCurrentPosition"
|
|
61
|
+
geolocation-key="coords.longitude"
|
|
62
|
+
placeholder="Longitude" />
|
|
63
|
+
<input
|
|
64
|
+
geolocation="getCurrentPosition"
|
|
65
|
+
geolocation-key="error.message"
|
|
66
|
+
placeholder="Error Message" />
|
|
67
|
+
<button actions="geoLocation.getCurrentPosition">
|
|
68
|
+
Get Location
|
|
69
|
+
</button>
|
|
70
|
+
</form>
|
|
71
|
+
<script src="./dist/CoCreate.js"></script>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
package/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import Actions from "@cocreate/actions";
|
|
2
|
+
import Api from "@cocreate/api";
|
|
3
|
+
|
|
4
|
+
const geolocationHandlers = {
|
|
5
|
+
getCurrentPosition,
|
|
6
|
+
watchPosition,
|
|
7
|
+
clearWatch
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Get Current Position
|
|
11
|
+
async function getCurrentPosition(action) {
|
|
12
|
+
if (action.element) action.options = await Api.getData(action);
|
|
13
|
+
|
|
14
|
+
navigator.geolocation.getCurrentPosition(
|
|
15
|
+
(position) => {
|
|
16
|
+
action.data = {
|
|
17
|
+
geoLocation: { coords: position.coords }
|
|
18
|
+
};
|
|
19
|
+
Api.setData(action);
|
|
20
|
+
dispatchEvent(action);
|
|
21
|
+
},
|
|
22
|
+
(error) => handleError(error, action),
|
|
23
|
+
action.options
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Watch Position
|
|
28
|
+
async function watchPosition(action) {
|
|
29
|
+
if (action.element) action.options = await Api.getData(action);
|
|
30
|
+
|
|
31
|
+
const watchId = navigator.geolocation.watchPosition(
|
|
32
|
+
(position) => {
|
|
33
|
+
// Use a closure to access `watchId`
|
|
34
|
+
action.data = {
|
|
35
|
+
geoLocation: {
|
|
36
|
+
id: watchId, // Access `watchId` from outer scope
|
|
37
|
+
coords: position.coords
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
Api.setData(action);
|
|
42
|
+
dispatchEvent(action);
|
|
43
|
+
},
|
|
44
|
+
(error) => handleError(error, action),
|
|
45
|
+
action.options
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Clear Watch
|
|
50
|
+
async function clearWatch(action) {
|
|
51
|
+
if (action.element) action.options = await Api.getData(action);
|
|
52
|
+
|
|
53
|
+
if (action.options.id) {
|
|
54
|
+
navigator.geolocation.clearWatch(Number(action.options.id));
|
|
55
|
+
dispatchEvent(action);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle Errors
|
|
60
|
+
function handleError(error, action) {
|
|
61
|
+
if (!action.data) action.data = { error };
|
|
62
|
+
else action.data.error = error;
|
|
63
|
+
console.error(`Geolocation error: ${error.message}`);
|
|
64
|
+
Api.setData(action);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function dispatchEvent(action) {
|
|
68
|
+
document.dispatchEvent(
|
|
69
|
+
new CustomEvent(action.endEvent, {
|
|
70
|
+
detail: {
|
|
71
|
+
data: action
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Actions Integration
|
|
78
|
+
Actions.init([
|
|
79
|
+
{
|
|
80
|
+
name: "geoLocation",
|
|
81
|
+
endEvent: "geolocation",
|
|
82
|
+
callback(action) {
|
|
83
|
+
geolocationHandlers[action.method](action);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
export default {
|
|
89
|
+
getCurrentPosition,
|
|
90
|
+
watchPosition,
|
|
91
|
+
clearWatch
|
|
92
|
+
};
|
package/src/index.js
CHANGED
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
* For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import observer from
|
|
26
|
-
import localStorage from
|
|
27
|
-
import
|
|
28
|
-
|
|
25
|
+
import observer from "@cocreate/observer";
|
|
26
|
+
import localStorage from "@cocreate/local-storage";
|
|
27
|
+
import "./web-share";
|
|
28
|
+
import "./geolocation";
|
|
29
|
+
// import "./theme";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Warn the page must be served over HTTPS
|
|
@@ -41,104 +42,114 @@ import './web-share'
|
|
|
41
42
|
// }
|
|
42
43
|
|
|
43
44
|
function init() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
45
|
+
const button = document.querySelector('[actions*="install"]');
|
|
46
|
+
|
|
47
|
+
window.addEventListener("beforeinstallprompt", (event) => {
|
|
48
|
+
window.deferredPrompt = event;
|
|
49
|
+
if (button) button.removeAttribute("hidden");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (button) {
|
|
53
|
+
button.addEventListener("click", async () => {
|
|
54
|
+
// console.log('👍', 'installBtn-clicked');
|
|
55
|
+
const promptEvent = window.deferredPrompt;
|
|
56
|
+
if (!promptEvent) {
|
|
57
|
+
// The deferred prompt isn't available.
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Show the install prompt.
|
|
61
|
+
promptEvent.prompt();
|
|
62
|
+
// Log the result
|
|
63
|
+
const result = await promptEvent.userChoice;
|
|
64
|
+
console.log("👍", "userChoice", result);
|
|
65
|
+
// Reset the deferred prompt variable, since
|
|
66
|
+
// prompt() can only be called once.
|
|
67
|
+
window.deferredPrompt = null;
|
|
68
|
+
// Hide the install button.
|
|
69
|
+
button.setAttribute("hidden", "");
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
window.addEventListener("appinstalled", (event) => {
|
|
74
|
+
// console.log('👍', 'appinstalled', event);
|
|
75
|
+
// Clear the deferredPrompt so it can be garbage collected
|
|
76
|
+
window.deferredPrompt = null;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if ("serviceWorker" in navigator) {
|
|
80
|
+
let workerPath;
|
|
81
|
+
if (window.CoCreateConfig && window.CoCreateConfig.serviceWorker)
|
|
82
|
+
workerPath = window.CoCreateConfig.serviceWorker;
|
|
83
|
+
else
|
|
84
|
+
workerPath =
|
|
85
|
+
localStorage.getItem("serviceWorker") || "/service-worker.js";
|
|
86
|
+
|
|
87
|
+
localStorage.setItem("serviceWorker", workerPath);
|
|
88
|
+
|
|
89
|
+
let cache;
|
|
90
|
+
if (window.CoCreateConfig && window.CoCreateConfig.cache)
|
|
91
|
+
cache = window.CoCreateConfig.cache;
|
|
92
|
+
else cache = localStorage.getItem("cache") || "true";
|
|
93
|
+
|
|
94
|
+
localStorage.setItem("cache", cache);
|
|
95
|
+
workerPath += `?cache=${cache}`;
|
|
96
|
+
|
|
97
|
+
let isPwa = true;
|
|
98
|
+
if (workerPath) {
|
|
99
|
+
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
|
100
|
+
if (registrations.length === 0 || isPwa == true)
|
|
101
|
+
window.addEventListener("load", function () {
|
|
102
|
+
navigator.serviceWorker
|
|
103
|
+
.getRegistration(workerPath)
|
|
104
|
+
.then((registration) => {
|
|
105
|
+
if (
|
|
106
|
+
registration &&
|
|
107
|
+
registration.active &&
|
|
108
|
+
registration.active.scriptURL.includes(
|
|
109
|
+
workerPath
|
|
110
|
+
)
|
|
111
|
+
) {
|
|
112
|
+
console.log("Service Worker Active");
|
|
113
|
+
} else {
|
|
114
|
+
navigator.serviceWorker
|
|
115
|
+
.register(workerPath)
|
|
116
|
+
.then((reg) => {
|
|
117
|
+
reg.onupdatefound = () => {
|
|
118
|
+
const installingWorker =
|
|
119
|
+
reg.installing;
|
|
120
|
+
installingWorker.onstatechange =
|
|
121
|
+
() => {
|
|
122
|
+
// console.log('Service Worker', installingWorker.state);
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
})
|
|
126
|
+
.catch((err) =>
|
|
127
|
+
console.log("SW ERROR", err)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
123
135
|
}
|
|
124
136
|
|
|
125
|
-
init()
|
|
137
|
+
init();
|
|
126
138
|
|
|
127
139
|
async function persistData() {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
if (navigator.storage && navigator.storage.persist) {
|
|
141
|
+
const result = await navigator.storage.persist();
|
|
142
|
+
// console.log(`Data persisted: ${result}`);
|
|
143
|
+
}
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
observer.init({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
name: "initPwa",
|
|
148
|
+
observe: ["addedNodes"],
|
|
149
|
+
selector: '[actions="install"]',
|
|
150
|
+
callback: (mutation) => {
|
|
151
|
+
init();
|
|
152
|
+
}
|
|
141
153
|
});
|
|
142
154
|
|
|
143
|
-
|
|
144
|
-
export default { persistData }
|
|
155
|
+
export default { persistData };
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
function applyThemeAttributes() {
|
|
2
|
+
// Detect the current theme (light or dark)
|
|
3
|
+
const isDarkMode = window.matchMedia(
|
|
4
|
+
"(prefers-color-scheme: dark)"
|
|
5
|
+
).matches;
|
|
6
|
+
|
|
7
|
+
// Select all elements with light or dark attributes
|
|
8
|
+
const elements = document.querySelectorAll("[light], [dark]");
|
|
9
|
+
|
|
10
|
+
elements.forEach((element) => {
|
|
11
|
+
// Determine the attribute to use based on the theme
|
|
12
|
+
const themeAttr = isDarkMode ? "dark" : "light";
|
|
13
|
+
const themeValue = element.getAttribute(themeAttr);
|
|
14
|
+
|
|
15
|
+
// Determine the current value of the attribute to update
|
|
16
|
+
let attributeToUpdate;
|
|
17
|
+
if (element.tagName === "LINK" && element.rel === "manifest") {
|
|
18
|
+
attributeToUpdate = "href";
|
|
19
|
+
} else if (
|
|
20
|
+
element.tagName === "META" &&
|
|
21
|
+
element.name === "theme-color"
|
|
22
|
+
) {
|
|
23
|
+
attributeToUpdate = "content";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (attributeToUpdate && themeValue) {
|
|
27
|
+
const currentValue = element.getAttribute(attributeToUpdate);
|
|
28
|
+
|
|
29
|
+
// Update only if the current value does not match the new value
|
|
30
|
+
if (currentValue !== themeValue) {
|
|
31
|
+
if (element.tagName === "LINK" && element.rel === "manifest") {
|
|
32
|
+
// Handle manifest link replacement
|
|
33
|
+
const newElement = element.cloneNode(); // Clone the element
|
|
34
|
+
newElement.setAttribute(attributeToUpdate, themeValue); // Update href
|
|
35
|
+
|
|
36
|
+
// Replace the old link with the new one to force fetch
|
|
37
|
+
element.parentNode.replaceChild(newElement, element);
|
|
38
|
+
console.log(`Manifest updated: ${themeValue}`);
|
|
39
|
+
} else {
|
|
40
|
+
// For other elements like meta, update the attribute directly
|
|
41
|
+
element.setAttribute(attributeToUpdate, themeValue);
|
|
42
|
+
if (element.tagName === "META") {
|
|
43
|
+
console.log(`Theme color updated: ${themeValue}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Apply theme attributes on page load
|
|
52
|
+
applyThemeAttributes();
|
|
53
|
+
|
|
54
|
+
// Listen for changes in the user's theme preference
|
|
55
|
+
window
|
|
56
|
+
.matchMedia("(prefers-color-scheme: dark)")
|
|
57
|
+
.addEventListener("change", applyThemeAttributes);
|