Initial commit

This commit is contained in:
Gryffin Winkler
2024-05-23 11:40:53 -04:00
commit 8a1acb4bd0
33 changed files with 851 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
<script setup>
import MyCounter from "./components/MyCounter.vue"
</script>
<template>
<MyCounter />
</template>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+58
View File
@@ -0,0 +1,58 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
+32
View File
@@ -0,0 +1,32 @@
<script setup>
import useToggleTheme from '../composables/useToggleTheme.js';
const [toggleTheme, themeStyles] = useToggleTheme(
{color:"red", backgroundColor:"black"},
{color:"green", textAlign:"right", backgroundColor:"gainsboro"}
);
</script>
<template>
<h3>MyCounter Component</h3>
<div>
<button @click="toggleTheme">Toggle Theme</button>
<p :style="themeStyles">Paragraph 1</p>
<p :style="themeStyles">Paragraph 2</p>
</div>
</template>
<style scoped>
p {
height : 30px;
padding-top: 10px;
padding-left: 10px;
padding-right: 10px;
}
</style>
+15
View File
@@ -0,0 +1,15 @@
<script setup>
import MyCounter from "./MyCounter.vue";
import { defineProps } from "vue";
const props = defineProps(["nb"]);
const nb = props.nb || 1;
</script>
<template>
<MyCounter v-for="i in nb" :key="i" :index="i" />
</template>
@@ -0,0 +1,25 @@
<script setup>
import useFetchCountries from "../composables/useFetchCountries"
import { ref } from "vue";
const data = ref();
const url = "https://restcountries.com/v3.1/all";
const [startFetch] = useFetchCountries(url);
const initData = async () => {
data.value = await startFetch();
}
</script>
<template>
<button @click="initData">Start Fetch</button>
<br><br>
<b>Data</b> :
<ul>
<li v-for="(country, i) in data" :key="i">{{country}}</li>
</ul>
</template>
+26
View File
@@ -0,0 +1,26 @@
<script setup>
import { ref } from "vue"
const name = ref("");
</script>
<template>
<h3>Input Form</h3>
Name: <input type="text" v-model.lazy="name" />
<br/><br/>
<h3>Reactive Variables</h3>
name: <b>{{name}}</b>
<br/><br/>
</template>
<style scoped>
h3 {
background-color: gainsboro;
padding: 5px;
}
</style>
+14
View File
@@ -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;
@@ -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;
@@ -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;
+10
View File
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
+21
View File
@@ -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;
+22
View File
@@ -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;
@@ -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;
@@ -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;
@@ -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;
+18
View File
@@ -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,
}
+24
View File
@@ -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;
+17
View File
@@ -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;
+27
View File
@@ -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;
@@ -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;
+19
View File
@@ -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;
+28
View File
@@ -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;
+141
View File
@@ -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;
+12
View File
@@ -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');