From 8a1acb4bd0bc32b0054e13e8b139d8e9e17d15dd Mon Sep 17 00:00:00 2001 From: Gryffin Winkler Date: Thu, 23 May 2024 11:40:53 -0400 Subject: [PATCH] Initial commit --- .gitattributes | 2 + Contributing.md | 14 ++ LICENSE.txt | 27 ++++ README.md | 16 ++ Source Code/src/App.vue | 11 ++ Source Code/src/assets/logo.png | Bin 0 -> 6849 bytes Source Code/src/components/HelloWorld.vue | 58 +++++++ Source Code/src/components/MyCounter.vue | 32 ++++ Source Code/src/components/MyCounters.vue | 15 ++ Source Code/src/components/MyCountries.vue | 25 ++++ Source Code/src/components/MyForm.vue | 26 ++++ Source Code/src/composables/useCounter.js | 14 ++ Source Code/src/composables/useCounterMax.js | 16 ++ .../src/composables/useCounterMaxWithError.js | 28 ++++ Source Code/src/composables/useFetch.js | 10 ++ .../src/composables/useFetchCountries.js | 17 +++ Source Code/src/composables/useFormatDate.js | 34 +++++ Source Code/src/composables/useGeolocation.js | 27 ++++ .../composables/useGeolocationWithDetails.js | 29 ++++ Source Code/src/composables/useMap.js | 21 +++ Source Code/src/composables/useMaximum.js | 22 +++ Source Code/src/composables/useToggleTheme.js | 17 +++ Source Code/src/composables/useUpperCase.js | 21 +++ Source Code/src/composables/useWindowSize.js | 27 ++++ Source Code/src/directives.js | 18 +++ Source Code/src/directives/clearable.js | 24 +++ Source Code/src/directives/color.js | 17 +++ Source Code/src/directives/focus.js | 27 ++++ Source Code/src/directives/integers-only.js | 56 +++++++ Source Code/src/directives/map.js | 19 +++ Source Code/src/directives/max-value.js | 28 ++++ Source Code/src/directives/timer.js | 141 ++++++++++++++++++ Source Code/src/main.js | 12 ++ 33 files changed, 851 insertions(+) create mode 100644 .gitattributes create mode 100644 Contributing.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 Source Code/src/App.vue create mode 100644 Source Code/src/assets/logo.png create mode 100644 Source Code/src/components/HelloWorld.vue create mode 100644 Source Code/src/components/MyCounter.vue create mode 100644 Source Code/src/components/MyCounters.vue create mode 100644 Source Code/src/components/MyCountries.vue create mode 100644 Source Code/src/components/MyForm.vue create mode 100644 Source Code/src/composables/useCounter.js create mode 100644 Source Code/src/composables/useCounterMax.js create mode 100644 Source Code/src/composables/useCounterMaxWithError.js create mode 100644 Source Code/src/composables/useFetch.js create mode 100644 Source Code/src/composables/useFetchCountries.js create mode 100644 Source Code/src/composables/useFormatDate.js create mode 100644 Source Code/src/composables/useGeolocation.js create mode 100644 Source Code/src/composables/useGeolocationWithDetails.js create mode 100644 Source Code/src/composables/useMap.js create mode 100644 Source Code/src/composables/useMaximum.js create mode 100644 Source Code/src/composables/useToggleTheme.js create mode 100644 Source Code/src/composables/useUpperCase.js create mode 100644 Source Code/src/composables/useWindowSize.js create mode 100644 Source Code/src/directives.js create mode 100644 Source Code/src/directives/clearable.js create mode 100644 Source Code/src/directives/color.js create mode 100644 Source Code/src/directives/focus.js create mode 100644 Source Code/src/directives/integers-only.js create mode 100644 Source Code/src/directives/map.js create mode 100644 Source Code/src/directives/max-value.js create mode 100644 Source Code/src/directives/timer.js create mode 100644 Source Code/src/main.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Contributing.md b/Contributing.md new file mode 100644 index 0000000..f6005ad --- /dev/null +++ b/Contributing.md @@ -0,0 +1,14 @@ +# Contributing to Apress Source Code + +Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. + +## How to Contribute + +1. Make sure you have a GitHub account. +2. Fork the repository for the relevant book. +3. Create a new branch on which to make your change, e.g. +`git checkout -b my_code_contribution` +4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. +5. Submit a pull request. + +Thank you for your contribution! \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..203e51f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Freeware License, some rights reserved + +Copyright (c) Eric Sarrion 2024 + +Permission is hereby granted, free of charge, to anyone obtaining a copy +of this software and associated documentation files (the "Software"), +to work with the Software within the limits of freeware distribution and fair use. +This includes the rights to use, copy, and modify the Software for personal use. +Users are also allowed and encouraged to submit corrections and modifications +to the Software for the benefit of other users. + +It is not allowed to reuse, modify, or redistribute the Software for +commercial use in any way, or for a user’s educational materials such as books +or blog articles without prior permission from the copyright holder. + +The above copyright notice and this permission notice need to be included +in all copies or substantial portions of the software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ee95ad --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Apress Source Code + +This repository accompanies [*Master Vue.js in 6 Days*](https://www.link.springer.com/book/10.1007/979-8-8688-0364-2) by Eric Sarrion (Apress, 2024). + +[comment]: #cover +![Cover image](979-8-8688-0363-5.jpg) + +Download the files as a zip using the green button, or clone the repository to your machine using Git. + +## Releases + +Release v1.0 corresponds to the code in the published book, without corrections or updates. + +## Contributions + +See the file Contributing.md for more information on how you can contribute to this repository. \ No newline at end of file diff --git a/Source Code/src/App.vue b/Source Code/src/App.vue new file mode 100644 index 0000000..7d931a1 --- /dev/null +++ b/Source Code/src/App.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/Source Code/src/assets/logo.png b/Source Code/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d2503fc2a44b5053b0837ebea6e87a2d339a43 GIT binary patch literal 6849 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 +
+

{{ msg }}

+

+ For a guide and recipes on how to configure / customize this project,
+ check out the + vue-cli documentation. +

+

Installed CLI Plugins

+ +

Essential Links

+ +

Ecosystem

+ +
+ + + + + + diff --git a/Source Code/src/components/MyCounter.vue b/Source Code/src/components/MyCounter.vue new file mode 100644 index 0000000..59a007e --- /dev/null +++ b/Source Code/src/components/MyCounter.vue @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/Source Code/src/components/MyCounters.vue b/Source Code/src/components/MyCounters.vue new file mode 100644 index 0000000..7962eeb --- /dev/null +++ b/Source Code/src/components/MyCounters.vue @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/Source Code/src/components/MyCountries.vue b/Source Code/src/components/MyCountries.vue new file mode 100644 index 0000000..5093013 --- /dev/null +++ b/Source Code/src/components/MyCountries.vue @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/Source Code/src/components/MyForm.vue b/Source Code/src/components/MyForm.vue new file mode 100644 index 0000000..e39f6e0 --- /dev/null +++ b/Source Code/src/components/MyForm.vue @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/Source Code/src/composables/useCounter.js b/Source Code/src/composables/useCounter.js new file mode 100644 index 0000000..511d619 --- /dev/null +++ b/Source Code/src/composables/useCounter.js @@ -0,0 +1,14 @@ +import { ref } from "vue"; + +const useCounter = (init) => { + const count = ref(init); + const increment = () => { + count.value++; + } + const decrement = () => { + count.value--; + } + return [count, increment, decrement]; +} + +export default useCounter; \ No newline at end of file diff --git a/Source Code/src/composables/useCounterMax.js b/Source Code/src/composables/useCounterMax.js new file mode 100644 index 0000000..b49b880 --- /dev/null +++ b/Source Code/src/composables/useCounterMax.js @@ -0,0 +1,16 @@ +import useCounter from "../composables/useCounter"; + +const useCounterMax = (init, max) => { + const [count, increment, decrement] = useCounter(init); + const incrementMax = () => { + if (count.value >= max) { + return; // Avoid incrementing + } + else { + increment(); // Increment + } + } + return [count, incrementMax, decrement]; +} + +export default useCounterMax; \ No newline at end of file diff --git a/Source Code/src/composables/useCounterMaxWithError.js b/Source Code/src/composables/useCounterMaxWithError.js new file mode 100644 index 0000000..d1ea297 --- /dev/null +++ b/Source Code/src/composables/useCounterMaxWithError.js @@ -0,0 +1,28 @@ +import useCounter from "../composables/useCounter"; +import { ref, onMounted } from "vue"; + +const useCounterMaxWithError = (init, max) => { + const [count, increment, decrement] = useCounter(init); + const error = ref(""); + const incrementMax = () => { + if (count.value >= max) { + error.value = "Maximum value reached!"; + } + else { + increment(); + error.value = ""; + } + } + const decrementMax = () => { + decrement(); + if (count.value <= max) { + error.value = ""; + } + } + onMounted(()=> { + if (count.value > max) error.value = "Maximum value reached!"; + }); + return [count, incrementMax, decrementMax, error]; +} + +export default useCounterMaxWithError; \ No newline at end of file diff --git a/Source Code/src/composables/useFetch.js b/Source Code/src/composables/useFetch.js new file mode 100644 index 0000000..bf3b1c9 --- /dev/null +++ b/Source Code/src/composables/useFetch.js @@ -0,0 +1,10 @@ +const useFetch = (url) => { + const startFetch = async () => { + const res = await fetch(url); + const d = await res.text(); + return JSON.parse(d); // Returning the data read from the server in JSON format + } + return [startFetch]; +} + +export default useFetch; \ No newline at end of file diff --git a/Source Code/src/composables/useFetchCountries.js b/Source Code/src/composables/useFetchCountries.js new file mode 100644 index 0000000..5a82864 --- /dev/null +++ b/Source Code/src/composables/useFetchCountries.js @@ -0,0 +1,17 @@ +import useFetch from "./useFetch"; + +const useFetchCountries = (url) => { + const [startFetch] = useFetch(url); + let countries; + const startFetchCountries = async () => { + const data = await startFetch(); + countries = data.map(function(elem) { + return elem.name.common; // Retain only the common.name property + }); + countries = countries.sort((n1, n2) => (n1 > n2)); // In ascending alphabetical order + return countries; + } + return [startFetchCountries]; +} + +export default useFetchCountries; \ No newline at end of file diff --git a/Source Code/src/composables/useFormatDate.js b/Source Code/src/composables/useFormatDate.js new file mode 100644 index 0000000..efebc23 --- /dev/null +++ b/Source Code/src/composables/useFormatDate.js @@ -0,0 +1,34 @@ +import { customRef } from "vue"; + +const formatDate = (date, format) => { + const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; + + if (format == "MM-DD-YYYY") + return date.toLocaleDateString('en-US', options).replace(/\//g, '-'); + else if (format == "DD-MM-YYYY") + return date.toLocaleDateString('en-GB', options).replace(/\//g, '-'); + else if (format == "MM/DD/YYYY") + return date.toLocaleDateString('en-US', options); + else if (format == "DD/MM/YYYY") + return date.toLocaleDateString('en-GB', options); +} + +const useFormatDate = (date, format) => { + return customRef((track, trigger) => { + let value = date; // value will be the tracked variable + return { + get() { + // track the dependency when the value is read + track(); + return formatDate(value, format); + }, + set(newValue) { + // update the value and trigger reactivity + value = newValue; + trigger(); + } + }; + }); +}; + +export default useFormatDate; \ No newline at end of file diff --git a/Source Code/src/composables/useGeolocation.js b/Source Code/src/composables/useGeolocation.js new file mode 100644 index 0000000..b5ef971 --- /dev/null +++ b/Source Code/src/composables/useGeolocation.js @@ -0,0 +1,27 @@ +import { ref, onMounted } from "vue"; + +const useGeolocation = () => { + const latitude = ref(null); + const longitude = ref(null); + + const handleGeolocation = (position) => { + latitude.value = position.coords.latitude; + longitude.value = position.coords.longitude; + }; + + const errorGeolocation = (error) => { + console.log("Geolocation error:", error.message); + }; + + onMounted(() => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(handleGeolocation, errorGeolocation); + } else { + console.log("Geolocation is not available in this browser."); + } + }); + + return [latitude, longitude]; +} + +export default useGeolocation; \ No newline at end of file diff --git a/Source Code/src/composables/useGeolocationWithDetails.js b/Source Code/src/composables/useGeolocationWithDetails.js new file mode 100644 index 0000000..6bfbcec --- /dev/null +++ b/Source Code/src/composables/useGeolocationWithDetails.js @@ -0,0 +1,29 @@ +import { ref, watchEffect } from "vue"; + +import useGeolocation from '../composables/useGeolocation'; + +const useGeolocationWithDetails = () => { + const [latitude, longitude] = useGeolocation(); + const country = ref(""); + const city = ref(""); + + // To find the country and city corresponding to the latitude/longitude + watchEffect(async ()=>{ + if (latitude.value && longitude.value) { + const response = await fetch( + `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude.value}&lon=${longitude.value}` + ); + const data = await response.json(); + if (data && data.address && data.address.country) { + country.value = data.address.country; + } + if (data && data.address) { + city.value = data.address.city || data.address.town; + } + } + }); + + return [latitude, longitude, country, city]; +} + +export default useGeolocationWithDetails; \ No newline at end of file diff --git a/Source Code/src/composables/useMap.js b/Source Code/src/composables/useMap.js new file mode 100644 index 0000000..5e3610f --- /dev/null +++ b/Source Code/src/composables/useMap.js @@ -0,0 +1,21 @@ +import L from "leaflet" + +const useMap = (latitude, longitude, idMap) => { + const zoom = 13; + + // To position the map at the indicated location + const map = L.map(idMap).setView([latitude, longitude], zoom); + + // To display the corresponding map + L.tileLayer("https://a.tile.openstreetmap.org/{z}/{x}/{y}.png", { + maxZoom: 20, + }).addTo(map); + + // To display a marker on the map to indicate the specified location + L.marker([latitude, longitude]).addTo(map); + + // We return the map object created by Leaflet + return map; +} + +export default useMap; \ No newline at end of file diff --git a/Source Code/src/composables/useMaximum.js b/Source Code/src/composables/useMaximum.js new file mode 100644 index 0000000..b9b4c97 --- /dev/null +++ b/Source Code/src/composables/useMaximum.js @@ -0,0 +1,22 @@ +import { customRef } from "vue"; + +const useMaximum = (max) => { + // Create a custom reference (customRef) + return customRef((track, trigger) => { + let value = 0; // value will be the variable being tracked, initialized here to 0. + return { + get() { + // Track the dependency when the value is read. + track(); + return value; + }, + set(newValue) { + // Update the value and trigger reactivity. + if (newValue <= max) value = newValue; + trigger(); + } + }; + }); +} + +export default useMaximum; \ No newline at end of file diff --git a/Source Code/src/composables/useToggleTheme.js b/Source Code/src/composables/useToggleTheme.js new file mode 100644 index 0000000..e88698d --- /dev/null +++ b/Source Code/src/composables/useToggleTheme.js @@ -0,0 +1,17 @@ +import { ref } from 'vue'; + +const useThemeToggle = (theme0, theme1) => { + const themes = [theme0, theme1]; + let theme = 0; // theme0 by default + const themeStyles = ref(themes[theme]); + + const toggleTheme = () => { + if (theme == 0) theme = 1; + else theme = 0; + themeStyles.value = themes[theme]; + }; + + return [toggleTheme, themeStyles]; +} + +export default useThemeToggle; \ No newline at end of file diff --git a/Source Code/src/composables/useUpperCase.js b/Source Code/src/composables/useUpperCase.js new file mode 100644 index 0000000..b359ce2 --- /dev/null +++ b/Source Code/src/composables/useUpperCase.js @@ -0,0 +1,21 @@ +import { customRef } from 'vue'; + +const useUpperCase = (initValue) => { + return customRef((track, trigger) => { + let value = initValue; // value will be the tracked variable + return { + get() { + // track the dependency when the value is read + track(); + return value.toUpperCase(); + }, + set(newValue) { + // update the value and trigger reactivity + value = newValue; + trigger(); + } + }; + }); +}; + +export default useUpperCase; \ No newline at end of file diff --git a/Source Code/src/composables/useWindowSize.js b/Source Code/src/composables/useWindowSize.js new file mode 100644 index 0000000..99a0c1a --- /dev/null +++ b/Source Code/src/composables/useWindowSize.js @@ -0,0 +1,27 @@ +import { ref, onMounted, onBeforeUnmount } from "vue"; + +const useWindowSize = () => { + const windowSize = ref({ + width: window.innerWidth, + height: window.innerHeight, + }); + + const updateWindowSize = () => { + windowSize.value = { + width: window.innerWidth, + height: window.innerHeight, + }; + }; + + onMounted(() => { + window.addEventListener('resize', updateWindowSize); + }); + + onBeforeUnmount(() => { + window.removeEventListener('resize', updateWindowSize); + }); + + return windowSize; +} + +export default useWindowSize; \ No newline at end of file diff --git a/Source Code/src/directives.js b/Source Code/src/directives.js new file mode 100644 index 0000000..46d205f --- /dev/null +++ b/Source Code/src/directives.js @@ -0,0 +1,18 @@ +import focus from "./directives/focus"; +import integersOnly from "./directives/integers-only"; +import maxValue from "./directives/max-value"; +import clearable from "./directives/clearable"; +import timer from "./directives/timer"; +import map from "./directives/map"; +import color from "./directives/color"; + +export default { + focus, + integersOnly, + maxValue, + clearable, + timer, + map, + color, +} + diff --git a/Source Code/src/directives/clearable.js b/Source Code/src/directives/clearable.js new file mode 100644 index 0000000..35b875c --- /dev/null +++ b/Source Code/src/directives/clearable.js @@ -0,0 +1,24 @@ +const clearable = { + mounted(el) { + const clearButton = document.createElement("button"); + clearButton.innerHTML = "Clear"; + clearButton.style = "position:relative; left:10px;"; + + // Handle the click on the button (clear the content of the input field). + clearButton.addEventListener("click", () => { + // Clear the content of the input field. + el.value = ""; + // Simulate an input event to mimic a keyboard key press + // (mandatory to ensure that the reactive variable linked to the input field is updated) + el.dispatchEvent(new Event("input")); + // Give focus to the input field + el.focus(); + }); + + // Insert the button after the input field + el.parentNode.insertBefore(clearButton, el.nextSibling); + } +}; + +export default clearable; + diff --git a/Source Code/src/directives/color.js b/Source Code/src/directives/color.js new file mode 100644 index 0000000..afb1180 --- /dev/null +++ b/Source Code/src/directives/color.js @@ -0,0 +1,17 @@ +const color = { + mounted(el, binding) { + let colorStyle; + if (binding.modifiers.toggle) { + const colors = binding.value; + el.addEventListener("click", () => { + if (colorStyle == colors[0]) colorStyle = colors[1]; + else if (colorStyle == colors[1]) colorStyle = colors[0]; + else colorStyle = colors[0]; + // Change the background color of the element + el.style.backgroundColor = colorStyle; + }); + } + }, +}; + +export default color; \ No newline at end of file diff --git a/Source Code/src/directives/focus.js b/Source Code/src/directives/focus.js new file mode 100644 index 0000000..75984a4 --- /dev/null +++ b/Source Code/src/directives/focus.js @@ -0,0 +1,27 @@ +const focusDirective = { + mounted(el, binding) { + const arg = binding.arg; + const value = binding.value; + // Position the handling of the focus and blur events + el.addEventListener("focus", () => { + if (arg == "color") el.style.color = value; + if (arg == "backgroundcolor") el.style.backgroundColor = value; + if (arg == "colors") { + el.style.color = value.color; + el.style.backgroundColor = value.backgroundcolor; + } + }); + el.addEventListener("blur", () => { + if (arg == "color") el.style.color = ""; + if (arg == "backgroundcolor") el.style.backgroundColor = ""; + if (arg == "colors") { + el.style.color = ""; + el.style.backgroundColor = ""; + } + }); + // and then give focus to the input field + el.focus(); + } +}; + +export default focusDirective; \ No newline at end of file diff --git a/Source Code/src/directives/integers-only.js b/Source Code/src/directives/integers-only.js new file mode 100644 index 0000000..f26712d --- /dev/null +++ b/Source Code/src/directives/integers-only.js @@ -0,0 +1,56 @@ +const integersOnly = { + mounted(el, binding) { + + if (binding.modifiers.upper) { + // Convert the displayed field to uppercase with an initial value + // (for this to work, the v-model directive must be written before this one in the input field) + el.value = el.value.toUpperCase(); + + // Simulate an input event to mimic a keyboard keypress + // (necessary for the reactive variable linked to the field to be updated) + el.dispatchEvent(new Event("input")); + } + + el.addEventListener("keydown", (event) => { + const numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; + const letters = ["a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F"]; + const moves = ["Backspace", "ArrowLeft", "ArrowRight", + "Delete", "Tab", "Home", "End"]; + + let authorized; // Allowed keys in the input field + + // Allow hexadecimal characters if the hexa modifier is present + if (binding.modifiers.hexa) authorized = [...numbers, ...letters, ...moves]; + else authorized = [...numbers, ...moves]; + + // If the key is not allowed, do not take it into account + if (!authorized.includes(event.key)) event.preventDefault(); + + // Handle the upper modifier + if (binding.modifiers.upper) { + // If the key is a hexadecimal letter, convert it to uppercase + if (letters.includes(event.key)) { + const start = el.selectionStart; + const end = el.selectionEnd; + const text = el.value; + + // Insert the character at the cursor position + const newText = text.substring(0, start) + event.key + text.substring(end); + // Update the value of the input field (in uppercase) + el.value = newText.toUpperCase(); + // Move the cursor after the inserted character + el.setSelectionRange(start + 1, start + 1); + + // Prevent further processing of the key (as it has already been handled above) + event.preventDefault(); + + // Simulate an input event to mimic a keyboard keypress + // (necessary for the reactive variable linked to the field to be updated) + el.dispatchEvent(new Event("input")); + } + } + }); + }, +} + +export default integersOnly; \ No newline at end of file diff --git a/Source Code/src/directives/map.js b/Source Code/src/directives/map.js new file mode 100644 index 0000000..cf029a1 --- /dev/null +++ b/Source Code/src/directives/map.js @@ -0,0 +1,19 @@ +import useMap from "../composables/useMap.js" + +const map = { + updated(el, binding) { + const latitude = binding.value.latitude; + const longitude = binding.value.longitude; + if (latitude && longitude) { + if (el._map) el._map.remove(); + el._map = useMap(latitude, longitude, el.id); + } + else if (el._map) { + el._map.remove(); + el._map = null; + } + } +} + +export default map; + diff --git a/Source Code/src/directives/max-value.js b/Source Code/src/directives/max-value.js new file mode 100644 index 0000000..92276c9 --- /dev/null +++ b/Source Code/src/directives/max-value.js @@ -0,0 +1,28 @@ +const treatment = (el, binding) => { + const maxValue = binding.value || 100; // 100 by default + const value = el.value || 0; // Value in the field + const bold = binding.modifiers.bold; + if (value > maxValue) { + el.style.color = "red"; + if (bold) { + el.style.fontWeight = "bold"; + el.style.fontFamily = "arial"; + } + } + else { + el.style.color = ""; + el.style.fontWeight = ""; // Removal of "bold" + el.style.fontFamily = ""; // Removal of "arial" + } +} + +const maxValue = { + mounted(el, binding) { + treatment(el, binding); + }, + updated(el, binding) { + treatment(el, binding); + }, +} + +export default maxValue; \ No newline at end of file diff --git a/Source Code/src/directives/timer.js b/Source Code/src/directives/timer.js new file mode 100644 index 0000000..babf388 --- /dev/null +++ b/Source Code/src/directives/timer.js @@ -0,0 +1,141 @@ +const timer = { + mounted(el, binding) { + const ms = binding.modifiers.ms; + const chrono = binding.modifiers.chrono; + + // Initialization of the clock or stopwatch. + if (!chrono) { + let time = getCurrentTime(ms); + el.innerHTML = time; + } + else { + if (!ms) el.innerHTML = "00:00:00"; + else el.innerHTML = "00:00:00.0"; + } + + setInterval(()=>{ + if (!chrono) { + let time = getCurrentTime(ms); + el.innerHTML = time; + } + else { + const chronoTime = getChronoTime(ms); + el.innerHTML = chronoTime; + } + }, 100); + }, +} + +function getCurrentTime(ms = false) { + const now = new Date(); + const hours = now.getHours().toString().padStart(2, '0'); + const minutes = now.getMinutes().toString().padStart(2, '0'); + const seconds = now.getSeconds().toString().padStart(2, '0'); + let formattedTime = `${hours}:${minutes}:${seconds}`; + + if (ms) { + const milliseconds = now.getMilliseconds().toString().slice(0, 1); // Obtaining tenths of a second + formattedTime += `.${milliseconds}`; + } + + return formattedTime; +} + +let startChronoTime = new Date(); // Starting time of the stopwatch + +function getChronoTime(ms = false) { + const now = new Date(); + const elapsedMilliseconds = now.getTime() - startChronoTime.getTime(); + + const hours = Math.floor(elapsedMilliseconds / (3600 * 1000)); + const remainingMilliseconds1 = elapsedMilliseconds % (3600 * 1000); + + const minutes = Math.floor(remainingMilliseconds1 / (60 * 1000)); + const remainingMilliseconds2 = remainingMilliseconds1 % (60 * 1000); + + const seconds = Math.floor(remainingMilliseconds2 / 1000); + + let formattedTime = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + + if (ms) { + const milliseconds = Math.floor(remainingMilliseconds2 % 1000); + const tenthsOfSecond = Math.floor((milliseconds % 1000) / 100); + formattedTime += `.${tenthsOfSecond.toString()}`; + } + + return formattedTime; +} + +export default timer; + + + +//~ const timer = { + //~ mounted(el, binding) { + //~ const ms = binding.modifiers.ms; + //~ const chrono = binding.modifiers.chrono; + + //~ // Initialization of the clock or stopwatch. + //~ if (!chrono) { + //~ let time = getCurrentTime(ms); + //~ el.innerHTML = time; + //~ } + //~ else { + //~ if (!ms) el.innerHTML = "00:00:00"; + //~ else el.innerHTML = "00:00:00.0"; + //~ } + + //~ setInterval(()=>{ + //~ if (!chrono) { + //~ let time = getCurrentTime(ms); + //~ el.innerHTML = time; + //~ } + //~ else { + //~ const chronoTime = getChronoTime(ms); + //~ el.innerHTML = chronoTime; + //~ } + //~ }, 100); + //~ }, +//~ } + +//~ function getCurrentTime(ms = false) { + //~ const now = new Date(); + //~ const hours = now.getHours().toString().padStart(2, '0'); + //~ const minutes = now.getMinutes().toString().padStart(2, '0'); + //~ const seconds = now.getSeconds().toString().padStart(2, '0'); + //~ let formattedTime = `${hours}:${minutes}:${seconds}`; + + //~ if (ms) { + //~ const milliseconds = now.getMilliseconds().toString().slice(0, 1); // Obtaining tenths of a second. + //~ formattedTime += `.${milliseconds}`; + //~ } + + //~ return formattedTime; +//~ } + +//~ let startChronoTime = new Date(); // Starting time of the stopwatch. + +//~ function getChronoTime(ms = false) { + //~ const now = new Date(); + //~ const elapsedMilliseconds = now.getTime() - startChronoTime.getTime(); + + //~ const hours = Math.floor(elapsedMilliseconds / (3600 * 1000)); + //~ const remainingMilliseconds1 = elapsedMilliseconds % (3600 * 1000); + + //~ const minutes = Math.floor(remainingMilliseconds1 / (60 * 1000)); + //~ const remainingMilliseconds2 = remainingMilliseconds1 % (60 * 1000); + + //~ const seconds = Math.floor(remainingMilliseconds2 / 1000); + + //~ let formattedTime = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + + //~ if (ms) { + //~ const milliseconds = Math.floor(remainingMilliseconds2 % 1000); + //~ const tenthsOfSecond = Math.floor((milliseconds % 1000) / 100); + //~ formattedTime += `.${tenthsOfSecond.toString()}`; + //~ } + + //~ return formattedTime; +//~ } + +//~ export default timer; \ No newline at end of file diff --git a/Source Code/src/main.js b/Source Code/src/main.js new file mode 100644 index 0000000..221c054 --- /dev/null +++ b/Source Code/src/main.js @@ -0,0 +1,12 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import directives from "./directives.js" + +const app = createApp(App); + +for (let name in directives) { + // Creation of the directive name within the application + app.directive(name, directives[name]); +} + +app.mount('#app'); \ No newline at end of file