Merged in feature/VBV-310-VBV-324-document-improvements (pull request #66)
Feature/VBV-310 VBV 324 document improvements Approved-by: Elia Bieri
This commit is contained in:
commit
a11c0d481f
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<Dialog :open="true" class="relative z-50" @close="close">
|
||||
<div class="fixed inset-0 bg-black/30" aria-hidden="true"></div>
|
||||
<div class="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel class="w-full max-w-2xl bg-white pt-8">
|
||||
<div class="px-8 pb-4">
|
||||
<DialogTitle class="relative mb-8 flex flex-row">{{ title }}</DialogTitle>
|
||||
<p v-html="content" />
|
||||
</div>
|
||||
<div class="flex flex-row-reverse gap-x-4 border-t border-t-gray-500 p-4">
|
||||
<button class="btn-primary" @click="confirm">Löschen</button>
|
||||
<button class="btn-secondary" @click="close">Abbrechen</button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/vue";
|
||||
|
||||
defineProps<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
content: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["confirm", "close"]);
|
||||
const close = () => {
|
||||
emit("close");
|
||||
};
|
||||
const confirm = () => {
|
||||
emit("confirm");
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "@headlessui/vue";
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/vue";
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
});
|
||||
|
||||
|
|
@ -20,20 +20,22 @@ function setIsOpen(value: boolean) {
|
|||
<Dialog :open="modelValue" class="relative z-50" @close="setIsOpen">
|
||||
<div class="fixed inset-0 bg-black/30" aria-hidden="true"></div>
|
||||
<div class="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel class="w-full max-w-2xl bg-white px-8 pb-4 pt-8">
|
||||
<DialogTitle class="relative mb-8 flex flex-row">
|
||||
<slot name="title"></slot>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-4 h-4 w-4 cursor-pointer"
|
||||
@click="setIsOpen(false)"
|
||||
>
|
||||
<it-icon-close></it-icon-close>
|
||||
</button>
|
||||
</DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
<DialogPanel class="w-full max-w-2xl bg-white pt-8">
|
||||
<div class="px-8 pb-4">
|
||||
<DialogTitle class="relative mb-8 flex flex-row">
|
||||
<slot name="title"></slot>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-4 h-4 w-4 cursor-pointer"
|
||||
@click="setIsOpen(false)"
|
||||
>
|
||||
<it-icon-close></it-icon-close>
|
||||
</button>
|
||||
</DialogTitle>
|
||||
|
||||
<slot name="body"></slot>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<slot name="footer" />
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
"chooseLearningSequence": "Bitte wähle eine Lernsequenz aus",
|
||||
"chooseName": "Bitte wähle einen Namen",
|
||||
"chooseSequence": "Wähle eine Lernsequenz aus",
|
||||
"deleteModalTitle": "Unterlage löschen",
|
||||
"deleteModalWarning": "Willst du die Unterlage <strong>\"{title}\"</strong> löschen?<br> Diese Aktion ist nicht umkehrbar.",
|
||||
"expertDescription": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.",
|
||||
"fileLabel": "Datei",
|
||||
"maxFileSize": "Maximale Dateigrösse: 20 MB",
|
||||
|
|
@ -38,6 +40,10 @@
|
|||
"modalNameInformation": "Max. 70 Zeichen",
|
||||
"selectFile": "Bitte wähle eine Datei aus",
|
||||
"title": "Unterlagen",
|
||||
"trainerDescription": "Finde auf Teams zusätzliche Inhalte für deinen Unterricht.",
|
||||
"trainerLinkSrc": "https://teams.microsoft.com",
|
||||
"trainerLinkText": "Inhalte auf Teams anschauen",
|
||||
"trainerTitle": "Begleitung für Trainer",
|
||||
"uploadErrorMessage": "Beim Hochladen ist ein Fehler aufgetreten. Bitte versuche es erneut.",
|
||||
"userDescription": "Hier findest du die Unterlagen, die dir die Fachexpertin zur Verfügung gestellt hat."
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,31 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
import ItModal from "@/components/ui/ItModal.vue";
|
||||
import * as log from "loglevel";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted } from "vue";
|
||||
import CircleDiagram from "./CircleDiagram.vue";
|
||||
import CircleOverview from "./CircleOverview.vue";
|
||||
import DocumentUploadForm from "./DocumentUploadForm.vue";
|
||||
import DocumentSection from "./DocumentSection.vue";
|
||||
import LearningSequence from "./LearningSequence.vue";
|
||||
|
||||
import { uploadCircleDocument } from "@/services/files";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { CourseSessionUser, DocumentUploadData } from "@/types";
|
||||
import type { CourseSessionUser } from "@/types";
|
||||
import { humanizeDuration } from "@/utils/humanizeDuration";
|
||||
import sumBy from "lodash/sumBy";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
courseSlug: string;
|
||||
circleSlug: string;
|
||||
profileUser?: CourseSessionUser;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
readonly: false,
|
||||
profileUser: undefined,
|
||||
|
|
@ -33,10 +31,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
log.debug("CirclePage created", props.readonly, props.profileUser);
|
||||
|
||||
const showUploadModal = ref(false);
|
||||
const showUploadErrorMessage = ref(false);
|
||||
const isUploading = ref(false);
|
||||
|
||||
const appStore = useAppStore();
|
||||
appStore.showMainNavigationBar = true;
|
||||
|
||||
|
|
@ -51,15 +45,6 @@ const duration = computed(() => {
|
|||
return "";
|
||||
});
|
||||
|
||||
const dropdownLearningSequences = computed(() =>
|
||||
circleStore.circle?.learningSequences.map((sequence) => ({
|
||||
id: sequence.id,
|
||||
name: sequence.title,
|
||||
}))
|
||||
);
|
||||
|
||||
watch(showUploadModal, (_v) => (showUploadErrorMessage.value = false));
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug(
|
||||
"CirclePage mounted",
|
||||
|
|
@ -107,28 +92,6 @@ onMounted(async () => {
|
|||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
async function uploadDocument(data: DocumentUploadData) {
|
||||
isUploading.value = true;
|
||||
showUploadErrorMessage.value = false;
|
||||
try {
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
throw new Error("No course session found");
|
||||
}
|
||||
const newDocument = await uploadCircleDocument(
|
||||
data,
|
||||
courseSessionsStore.currentCourseSession.id
|
||||
);
|
||||
const courseSessionStore = useCourseSessionsStore();
|
||||
courseSessionStore.addDocument(newDocument);
|
||||
showUploadModal.value = false;
|
||||
isUploading.value = false;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
showUploadErrorMessage.value = true;
|
||||
isUploading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -213,60 +176,7 @@ async function uploadDocument(data: DocumentUploadData) {
|
|||
{{ $t("circlePage.learnMore") }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="!props.readonly" class="mt-8 block border p-6">
|
||||
<h3 class="text-blue-dark">
|
||||
{{ $t("circlePage.documents.title") }}
|
||||
</h3>
|
||||
<div v-if="!courseSessionsStore.canUploadCircleDocuments">
|
||||
<div class="mt-4 leading-relaxed">
|
||||
{{ $t("circlePage.documents.userDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
<ol
|
||||
v-if="
|
||||
courseSessionsStore &&
|
||||
courseSessionsStore.circleDocuments &&
|
||||
courseSessionsStore.circleDocuments.length > 0
|
||||
"
|
||||
>
|
||||
<li
|
||||
v-for="learningSequence of courseSessionsStore.circleDocuments"
|
||||
:key="learningSequence.id"
|
||||
>
|
||||
<h4 class="text-bold mt-4">{{ learningSequence.title }}</h4>
|
||||
<ul>
|
||||
<li
|
||||
v-for="document of learningSequence.documents"
|
||||
:key="document.url"
|
||||
>
|
||||
<a :href="document.url" download>
|
||||
<span>{{ document.name }}</span>
|
||||
</a>
|
||||
<button
|
||||
v-if="courseSessionsStore.canUploadCircleDocuments"
|
||||
type="button"
|
||||
class="relative top-[1px] ml-2 inline-block h-3 w-3 cursor-pointer leading-6"
|
||||
@click="courseSessionsStore.removeDocument(document.id)"
|
||||
>
|
||||
<it-icon-close class="h-3 w-3"></it-icon-close>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<div v-if="courseSessionsStore.canUploadCircleDocuments">
|
||||
<div class="mt-4 leading-relaxed">
|
||||
{{ $t("circlePage.documents.expertDescription") }}
|
||||
</div>
|
||||
<button
|
||||
class="btn-primary mt-4 text-xl"
|
||||
@click="showUploadModal = true"
|
||||
>
|
||||
{{ $t("circlePage.documents.action") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DocumentSection v-if="!readonly" />
|
||||
<div v-if="!props.readonly" class="expert mt-8 border p-6">
|
||||
<h3 class="text-blue-dark">{{ $t("circlePage.gotQuestions") }}</h3>
|
||||
<div class="mt-4 leading-relaxed">
|
||||
|
|
@ -312,17 +222,6 @@ async function uploadDocument(data: DocumentUploadData) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ItModal v-model="showUploadModal">
|
||||
<template #title>{{ $t("circlePage.documents.action") }}</template>
|
||||
<template #body>
|
||||
<DocumentUploadForm
|
||||
:learning-sequences="dropdownLearningSequences"
|
||||
:show-upload-error-message="showUploadErrorMessage"
|
||||
:is-uploading="isUploading"
|
||||
@form-submit="uploadDocument"
|
||||
/>
|
||||
</template>
|
||||
</ItModal>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<li
|
||||
:key="doc.url"
|
||||
class="grid grid-cols-document-list-item border-b border-b-gray-500 py-3 grid-areas-document-list-item"
|
||||
>
|
||||
<h3 class="grid-in-title">
|
||||
{{ doc.name }}
|
||||
</h3>
|
||||
<h4 class="grid-in-subtitle">{{ subtitle }}</h4>
|
||||
<div class="flex items-center justify-end gap-x-4 grid-in-icons">
|
||||
<a v-if="canDelete" class="flex cursor-pointer" @click="emit('delete')">
|
||||
<it-icon-delete class="h-8 w-8" />
|
||||
</a>
|
||||
|
||||
<a download :href="doc.url" class="flex">
|
||||
<it-icon-download class="h-8 w-8" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
doc: {
|
||||
url: string;
|
||||
name: string;
|
||||
};
|
||||
canDelete: boolean;
|
||||
subtitle: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["delete"]);
|
||||
</script>
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div class="mt-8 block border p-6">
|
||||
<h3 class="text-blue-dark">
|
||||
{{ $t("circlePage.documents.title") }}
|
||||
</h3>
|
||||
<div>
|
||||
<div class="mt-4 leading-relaxed">
|
||||
<template v-if="!courseSessionsStore.canUploadCircleDocuments">
|
||||
{{ $t("circlePage.documents.userDescription") }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t("circlePage.documents.expertDescription") }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
v-if="courseSessionsStore.circleDocuments.length"
|
||||
class="mt-8 border-t border-t-gray-500"
|
||||
>
|
||||
<template
|
||||
v-for="learningSequence of courseSessionsStore.circleDocuments"
|
||||
:key="learningSequence.id"
|
||||
>
|
||||
<DocumentListItem
|
||||
v-for="doc of learningSequence.documents"
|
||||
:key="doc.url"
|
||||
:subtitle="learningSequence.title"
|
||||
:can-delete="courseSessionsStore.canUploadCircleDocuments"
|
||||
:doc="doc"
|
||||
@delete="deleteDocument(doc)"
|
||||
/>
|
||||
</template>
|
||||
</ul>
|
||||
<div v-if="courseSessionsStore.canUploadCircleDocuments">
|
||||
<button class="btn-primary mt-8 text-xl" @click="showUploadModal = true">
|
||||
{{ $t("circlePage.documents.action") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ItModal v-model="showUploadModal">
|
||||
<template #title>{{ $t("circlePage.documents.action") }}</template>
|
||||
<template #body>
|
||||
<DocumentUploadForm
|
||||
:learning-sequences="dropdownLearningSequences"
|
||||
:show-upload-error-message="showUploadErrorMessage"
|
||||
:is-uploading="isUploading"
|
||||
@form-submit="uploadDocument"
|
||||
/>
|
||||
</template>
|
||||
</ItModal>
|
||||
<div
|
||||
v-if="courseSessionsStore.canUploadCircleDocuments"
|
||||
class="mt-8 flex flex-col gap-y-4 border p-6"
|
||||
>
|
||||
<h3 class="text-blue-dark">
|
||||
{{ $t("circlePage.documents.trainerTitle") }}
|
||||
</h3>
|
||||
<div class="leading-relaxed">
|
||||
{{ $t("circlePage.documents.trainerDescription") }}
|
||||
</div>
|
||||
<a target="_blank" class="link" :href="$t('circlePage.documents.trainerLinkSrc')">
|
||||
{{ $t("circlePage.documents.trainerLinkText") }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ItModal from "@/components/ui/ItModal.vue";
|
||||
import { uploadCircleDocument } from "@/services/files";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
||||
import dialog from "@/utils/confirm-dialog";
|
||||
import log from "loglevel";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import DocumentListItem from "./DocumentListItem.vue";
|
||||
import DocumentUploadForm from "./DocumentUploadForm.vue";
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const circleStore = useCircleStore();
|
||||
const showUploadModal = ref(false);
|
||||
const showUploadErrorMessage = ref(false);
|
||||
const isUploading = ref(false);
|
||||
const dropdownLearningSequences = computed(() =>
|
||||
circleStore.circle?.learningSequences.map((sequence) => ({
|
||||
id: sequence.id,
|
||||
name: sequence.title,
|
||||
}))
|
||||
);
|
||||
|
||||
// confirm dialog
|
||||
const { t } = useI18n();
|
||||
const deleteDocument = async (doc: CircleDocument) => {
|
||||
const options = {
|
||||
title: t("circlePage.documents.deleteModalTitle"),
|
||||
content: t("circlePage.documents.deleteModalWarning", { title: doc.name }),
|
||||
};
|
||||
try {
|
||||
await dialog.confirm(options);
|
||||
courseSessionsStore.removeDocument(doc.id);
|
||||
} catch (e) {
|
||||
log.debug("rejected");
|
||||
}
|
||||
};
|
||||
watch(showUploadModal, () => (showUploadErrorMessage.value = false));
|
||||
async function uploadDocument(data: DocumentUploadData) {
|
||||
isUploading.value = true;
|
||||
showUploadErrorMessage.value = false;
|
||||
try {
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
throw new Error("No course session found");
|
||||
}
|
||||
const newDocument = await uploadCircleDocument(
|
||||
data,
|
||||
courseSessionsStore.currentCourseSession.id
|
||||
);
|
||||
const courseSessionStore = useCourseSessionsStore();
|
||||
courseSessionStore.addDocument(newDocument);
|
||||
showUploadModal.value = false;
|
||||
isUploading.value = false;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
showUploadErrorMessage.value = true;
|
||||
isUploading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -4,7 +4,7 @@ import type { DocumentUploadData, DropdownSelectable } from "@/types";
|
|||
import { reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
interface Props {
|
||||
export interface Props {
|
||||
learningSequences: DropdownSelectable[];
|
||||
showUploadErrorMessage: boolean;
|
||||
isUploading: boolean;
|
||||
|
|
|
|||
|
|
@ -182,21 +182,23 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
const circleDocuments = computed(() => {
|
||||
const circleStore = useCircleStore();
|
||||
|
||||
return circleStore.circle?.learningSequences
|
||||
.map((ls) => ({ id: ls.id, title: ls.title, documents: [] }))
|
||||
.map((ls: { id: number; title: string; documents: CircleDocument[] }) => {
|
||||
if (currentCourseSession.value === undefined) {
|
||||
return ls;
|
||||
}
|
||||
|
||||
for (const document of currentCourseSession.value.documents) {
|
||||
if (document.learning_sequence === ls.id) {
|
||||
ls.documents.push(document);
|
||||
return (
|
||||
circleStore.circle?.learningSequences
|
||||
.map((ls) => ({ id: ls.id, title: ls.title, documents: [] }))
|
||||
.map((ls: { id: number; title: string; documents: CircleDocument[] }) => {
|
||||
if (currentCourseSession.value === undefined) {
|
||||
return ls;
|
||||
}
|
||||
}
|
||||
return ls;
|
||||
})
|
||||
.filter((ls) => ls.documents.length > 0);
|
||||
|
||||
for (const document of currentCourseSession.value.documents) {
|
||||
if (document.learning_sequence === ls.id) {
|
||||
ls.documents.push(document);
|
||||
}
|
||||
}
|
||||
return ls;
|
||||
})
|
||||
.filter((ls) => ls.documents.length > 0) || []
|
||||
);
|
||||
});
|
||||
|
||||
function addDocument(document: CircleDocument) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import ConfirmDialog from "@/components/ui/ConfirmDialog.vue";
|
||||
import { createApp } from "vue";
|
||||
|
||||
interface ConfirmDialogOptions {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
type ResolveReject = (v?: unknown) => void;
|
||||
|
||||
// inspired by https://stackoverflow.com/a/69773076/6071058
|
||||
/*
|
||||
* We need a separate service for the ConfirmDialog, because we don't want the
|
||||
* boilerplate of including the component and the handling of the promises inside
|
||||
* of every component where we need a confirm dialog.
|
||||
*
|
||||
* With this service, one can simply import the dialog and use it, e.g.
|
||||
*
|
||||
import dialog from "@/utils/confirm-dialog";
|
||||
const someMethodToConfirm = async () => {
|
||||
const options = {
|
||||
title: 'Dialog Title',
|
||||
content: 'Do you really wanna?'
|
||||
};
|
||||
try {
|
||||
await dialog.confirm(options);
|
||||
doSomethingWhenConfirmed();
|
||||
} catch (e) {
|
||||
log.debug("rejected");
|
||||
doSomethingWhenRejected()
|
||||
}
|
||||
};
|
||||
|
||||
*/
|
||||
export default {
|
||||
confirm(options: ConfirmDialogOptions) {
|
||||
const mountEl = document.createElement("div");
|
||||
document.body.appendChild(mountEl);
|
||||
|
||||
let _resolve: ResolveReject, _reject: ResolveReject;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
_resolve = resolve;
|
||||
_reject = reject;
|
||||
});
|
||||
|
||||
const cleanUp = () => {
|
||||
mountEl?.parentNode?.removeChild(mountEl);
|
||||
dialog.unmount();
|
||||
};
|
||||
|
||||
const dialog = createApp(ConfirmDialog, {
|
||||
isOpen: true,
|
||||
title: options.title,
|
||||
content: options.content,
|
||||
onClose() {
|
||||
cleanUp();
|
||||
_reject();
|
||||
},
|
||||
onConfirm() {
|
||||
cleanUp();
|
||||
_resolve();
|
||||
},
|
||||
});
|
||||
dialog.mount(mountEl);
|
||||
return promise;
|
||||
},
|
||||
};
|
||||
|
|
@ -51,11 +51,13 @@ module.exports = {
|
|||
],
|
||||
"rating-scale-slim": ["bar bar bar", "fst mid fth"],
|
||||
"icon-card": ["icon title", "icon value"],
|
||||
"document-list-item": ["title icons", "subtitle icons"],
|
||||
},
|
||||
gridTemplateColumns: {
|
||||
"horizontal-bar-chart": "50px 1fr 300px 4fr 300px 1fr",
|
||||
"horizontal-bar-chart-slim": "50px 1fr 78px 4fr 78px 1fr",
|
||||
"icon-card": "60px auto",
|
||||
"document-list-item": "1fr 100px",
|
||||
},
|
||||
gridTemplateRows: {
|
||||
"horizontal-bar-chart": "200px 40px",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Outline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||
<path d="m25.63,7.06h-6.08v-1.05c0-1.18-.96-2.14-2.14-2.14h-4.83c-1.18,0-2.14.96-2.14,2.14v1.05h-6.08v1.5h2.61l.82,15.54c.06,1.14,1,2.03,2.14,2.03h10.14c1.14,0,2.08-.89,2.14-2.03l.82-15.54h2.61v-1.5Zm-13.69-1.05c0-.35.29-.64.64-.64h4.83c.35,0,.64.29.64.64v1.05h-6.11v-1.05Zm8.76,18.02c-.02.34-.3.61-.64.61h-10.14c-.34,0-.62-.27-.64-.61l-.81-15.46h13.04l-.81,15.46Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 489 B |
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M15,22.31c.19,0,.38-.07,.53-.22l7.45-7.45-1.06-1.06-6.17,6.17V3.83h-1.5v15.92l-6.17-6.17-1.06,1.06,7.45,7.45c.15,.15,.34,.22,.53,.22Z"/><rect x="4.94" y="24.84" width="20.11" height="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 309 B |
Loading…
Reference in New Issue