<script setup>
import ShoppingCart from "./ShoppingCart.vue";
import {ref} from "vue";
const isOpened = ref(false);
</script>
<template>
<button @click="isOpened = true">Open cart</button>
<ShoppingCart :is-opened="isOpened" @toggle="(value) => isOpened = value"/>
</template>
<script setup>
import axios from "axios";
import Player from "@vimeo/player";
import {throttle} from "lodash";
import ForwardIcon from "./svgs/ForwardIcon";
import BackwardIcon from "./svgs/BackwardIcon";
import LoadingIcon from "./svgs/LoadingIcon";
import {computed, onMounted, onUnmounted, ref, useSlots} from "vue";
const props = defineProps({
lessonId: Number,
trackProgress: Boolean
});
const speedIndex = ref(1);
let player = null;
const speedOptions = [0.75, 1, 1.25, 1.5, 1.75, 2.0];
const currentSpeed = computed(() => speedOptions[speedIndex.value]);
if (window.localStorage.getItem('speedIndex')) {
speedIndex.value = parseInt(window.localStorage.getItem('speedIndex'));
}
onMounted(() => {
player = new Player(useSlots().default()[0].el);
player.on('play', () => player.setPlaybackRate(currentSpeed.value));
if (props.trackProgress) {
player.on('progress', throttle((event) => updateProgress(event.percent), 10000));
player.on('ended', () => updateProgress(1));
}
});
onUnmounted(() => {
player.off('play');
player.off('progress');
player.off('ended');
});
function updateProgress(percent) {
return axios.put(`/lessons/${props.lessonId}/progress`, {percent: percent});
}
function toggleSpeed() {
if (typeof speedOptions[speedIndex.value + 1] !== "undefined") {
speedIndex.value += 1;
} else {
speedIndex.value = 0;
}
window.localStorage.setItem('speedIndex', speedIndex.value);
player.setPlaybackRate(currentSpeed.value);
}
async function goForward(seconds) {
const currentTime = await player.getCurrentTime();
const totalDuration = await player.getDuration();
player.setCurrentTime(Math.min(totalDuration, (currentTime + seconds)));
}
async function goBack(seconds) {
const currentTime = await player.getCurrentTime();
player.setCurrentTime(Math.max(0, (currentTime - seconds)));
}
</script>
<template>
<div class="relative aspect-w-16 aspect-h-9">
<div class="flex absolute inset-0 justify-center items-center">
<LoadingIcon class="w-14 h-14 text-white animate-spin"></LoadingIcon>
</div>
<slot></slot>
</div>
<div class="flex justify-end">
<div class="grid grid-cols-5 w-full max-w-xl divide-x divide-gray-700">
<button
class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
@click="toggleSpeed()">
<span class="hidden sm:inline-block">Speed:</span><span>{{ currentSpeed }}x</span>
</button>
<button
class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
@click="goBack(5)">
<BackwardIcon class="w-4 h-4"/>
<span>5s</span>
</button>
<button
class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
@click="goForward(5)">
<span>5s</span>
<ForwardIcon class="w-4 h-4"/>
</button>
<button
class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
@click="goBack(10)">
<BackwardIcon class="w-4 h-4"/>
<span>10s</span>
</button>
<button
class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
@click="goForward(10)">
<span>10s</span>
<ForwardIcon class="w-4 h-4"/>
</button>
</div>
</div>
</template>
import {onUnmounted} from "vue";
export default function (src) {
return new Promise((resolve, reject) => {
let script = document.querySelector(`script[src="${src}"]`);
if (!script) {
script = document.createElement("script");
script.src = src;
script.async = true;
script.setAttribute("data-status", "loading");
document.head.append(script);
}
if (script.getAttribute("data-status") === "loaded") {
resolve();
}
function onScriptLoad() {
resolve();
script.setAttribute("data-status", "loaded");
}
function onScriptError() {
reject();
script.setAttribute("data-status", "error");
}
script.addEventListener("load", onScriptLoad);
script.addEventListener("error", onScriptError);
onUnmounted(() => {
if (document.head.contains(script)) {
script.removeEventListener("load", onScriptLoad);
script.removeEventListener("error", onScriptError);
document.head.removeChild(script);
}
})
});
}
<template>
<form class="my-20 mx-auto max-w-2xl">
<h3 class="mb-2 mt-6 text-lg font-medium">Personal info</h3>
<div class="grid grid-cols-2 gap-6 mb-6">
<label class="block text-sm font-medium text-gray-700">
First name
<input
class="block mt-1 w-full text-sm placeholder-gray-400 rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500"
type="text"
v-model="form.firstName"
placeholder="First name"
/>
</label>
<label class="block text-sm font-medium text-gray-700">
Last name
<input
class="block mt-1 w-full text-sm placeholder-gray-400 rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500"
type="text"
v-model="form.lastName"
placeholder="Last name"
/>
</label>
<label class="block text-sm font-medium text-gray-700">
E-mail
<input
class="block mt-1 w-full text-sm placeholder-gray-400 rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500"
type="email"
v-model="form.email"
placeholder="E-mail address"
/>
</label>
</div>
<AddressFieldGroup
label="Delivery address"
v-model:street="form.deliveryAddress.street"
v-model:streetNumber="form.deliveryAddress.streetNumber"
v-model:postcode="form.deliveryAddress.postcode"
v-model:city="form.deliveryAddress.city"
/>
<AddressFieldGroup
label="Billing address"
v-model:street="form.billingAddress.street"
v-model:streetNumber="form.billingAddress.streetNumber"
v-model:postcode="form.billingAddress.postcode"
v-model:city="form.billingAddress.city"
/>
</form>
</template>
<script>
import {ref} from 'vue';
import AddressFieldGroup from './AddressFieldGroup.vue';
export default {
components: {AddressFieldGroup},
setup() {
const form = ref({
firstName: '321321',
lastName: '321321',
email: '321321',
deliveryAddress: {
street: '321321',
streetNumber: '321',
postcode: '321',
city: '321',
},
billingAddress: {
street: '',
streetNumber: '',
postcode: '',
city: '',
},
});
return {
form,
};
},
};
</script>
<script>
export default {
data() {
return {
status: 'idle',
};
},
methods: {
copy(text) {
// create textarea
const el = document.createElement('textarea');
// assign value
el.value = text;
// style textarea
el.style.position = 'absolute';
el.style.left = '-90000px';
document.body.appendChild(el);
// select its contents
el.select();
// execute copy
document.execCommand("copy");
// remove textarea
document.body.removeChild(el);
this.status = "copied";
setTimeout(() => this.status = "idle", 1000);
},
},
render() {
return this.$slots.default({
status: this.status,
copy: this.copy
});
}
};
</script>
// Parallel
async function getData() {
const [user, posts] = await Promise.all([
fetchUserData(),
fetchPostsData()
]);
console.log(user, posts);
}
// non parallel
function getData() {
return Promise.all([fetchUser(), fetchPosts()])
.then(([user, posts]) => {
console.log(user, posts);
})
.catch((error) => {
console.error(error);
});
}
<script setup>
import { useSlots, onMounted, ref } from "vue";
const test = ref()
onMounted(() => {
// 1
console.log(test.value.children[0])
// 2 nebo bez refu primo ze slotu useSlots().default()[0].el
})
</script>
<template>
<!-- nejde ref umistit primo na slot, nutno to takto obalit, nebo viz druhy zpusob -->
<div ref="test">
<slot />
</div>
</template>
// https://tailwindcss.com/docs/plugins#static-variants
// https://tallpad.com/series/tailwind/lessons/styling-vue-components-using-tailwindcss-custom-variants
// https://play.tailwindcss.com/8Pmxm8TomW
const plugin = require('tailwindcss/plugin')
/** @type {import('tailwindcss').Config} */
export default {
theme: {
extend: {
// ...
},
},
plugins: [
plugin(function ({ addVariant }) {
addVariant('hocus', ['&:hover', '&:focus'])
addVariant('error', ['&[data-error=true]', '[data-error=true] &'])
}),
],
}
<!--
https://github.com/tailwindlabs/tailwindcss-aspect-ratio/blob/master/src/index.js#L39
https://play.tailwindcss.com/4cuLPqCv05
https://www.youtube.com/watch?v=sxxUK0l8jKE
-->
<div class="p-20 flex justify-center items-center gap-12">
<img class="avatar-sm" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
<img class="avatar" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
<img class="avatar-lg" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
<img class="avatar-xl" src="https://res.cloudinary.com/thirus/image/upload/v1705061543/images/avatar.png" alt="" />
</div>
<!-- https://play.tailwindcss.com/CF9WdM43Jf -->
<section class="bg-slate-900 min-h-screen text-slate-100">
<!-- Shopping Cart -->
<div class="rounded-md bg-slate-800 p-8 grid grid-cols-[auto_1fr_auto_auto_auto] gap-6">
<div class="grid gap-6 col-span-5 grid-cols-subgrid">
<p class="col-span-2">Product</p>
<p>Price</p>
<p>Quantity</p>
<p class="text-right">Total</p>
</div>
<div class="grid gap-6 col-span-5 grid-cols-subgrid">
<img
class="size-20 object-cover"
src="https://tinyurl.com/3r25tr36"
alt=""
/>
<div>
<h3 class="text-xl font-medium">
Stylish Tote Bag
</h3>
<p class="text-sm text-slate-400">
Women's Tote Bag Brown
</p>
<span class="text-sm text-slate-500">
#368798
</span>
</div>
<p class="text-slate-400">
$99.00
</p>
<label>
<input
class="border border-slate-600 bg-transparent px-2 py-1 text-sm text-slate-400"
type="text"
value="1"
size="2"
/>
</label>
<p class="font-medium text-right">
$99.00
</p>
</div>
<div class="grid gap-6 col-span-5 grid-cols-subgrid">
<img
class="size-20 object-cover"
src="https://tinyurl.com/3pj5teex"
alt=""
/>
<div>
<h3 class="text-xl font-medium">
Sunglasses
</h3>
<p class="text-sm text-slate-400">
Wooden Frame
</p>
<span class="text-sm text-slate-500">
#756328
</span>
</div>
<p class="text-slate-400">
$102.00
</p>
<label>
<input
class="border border-slate-600 bg-transparent px-2 py-1 text-sm text-slate-400"
type="text"
value="10"
size="2" />
</label>
<p class="font-medium text-right">
$1020.00
</p>
</div>
</div>
</section>