mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-13 09:28:14 +08:00
fix(confirm): 完善confirm组件,谢谢岛风酱 @frg2089
Co-authored-by: 舰队的偶像-岛风酱! <frg2089@outlook.com> Signed-off-by: 舰队的偶像-岛风酱! <frg2089@outlook.com>
This commit is contained in:
@@ -1,67 +1,72 @@
|
||||
<template>
|
||||
<v-overlay v-model="showVal">
|
||||
<v-overlay v-model="visible">
|
||||
<div class="confirm-div">
|
||||
<div class="confirm-box">
|
||||
<div class="confirm-title">{{ title }}</div>
|
||||
<div class="confirm-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="confirm-btn-box">
|
||||
<button class="confirm-btn" @click="setCancel">
|
||||
<button class="confirm-btn" @click="onCancel">
|
||||
<img class="btn-icon" src="../assets/icons/circle-cancel.svg" alt="cancel" />
|
||||
<span class="btn-text">{{ cancel }}</span>
|
||||
<span class="btn-text">
|
||||
{{ cancel }}
|
||||
</span>
|
||||
</button>
|
||||
<button class="confirm-btn" @click="setConfirm">
|
||||
<button class="confirm-btn" @click="onConfirm">
|
||||
<img class="btn-icon" src="../assets/icons/circle-check.svg" alt="confirm" />
|
||||
<span class="btn-text">{{ confirm }}</span>
|
||||
<span class="btn-text">
|
||||
{{ confirm }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-overlay>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string;
|
||||
cancel?: string;
|
||||
confirm?: string;
|
||||
value: boolean;
|
||||
}>(),
|
||||
{
|
||||
title: "确认",
|
||||
cancel: "取消",
|
||||
confirm: "确定",
|
||||
value: false,
|
||||
}
|
||||
);
|
||||
interface TConfirmProps {
|
||||
title: string;
|
||||
cancel?: string;
|
||||
confirm?: string;
|
||||
/** 此值为 true 时显示对话框 */
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
const showVal = ref(false);
|
||||
interface TConfirmEmits {
|
||||
(e: "update:show", v: boolean): void;
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "confirm"): void;
|
||||
(e: "cancel"): void;
|
||||
}
|
||||
|
||||
// emits
|
||||
const emitConfirm = defineEmits(["update:value"]);
|
||||
|
||||
// expose
|
||||
defineExpose({
|
||||
showConfirm,
|
||||
const emits = defineEmits<TConfirmEmits>();
|
||||
const props = withDefaults(defineProps<TConfirmProps>(), {
|
||||
title: "确认",
|
||||
cancel: "取消",
|
||||
confirm: "确定",
|
||||
});
|
||||
|
||||
// methods
|
||||
function showConfirm() {
|
||||
showVal.value = true;
|
||||
}
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: v => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
function setCancel() {
|
||||
emitConfirm("update:value", false);
|
||||
showVal.value = false;
|
||||
}
|
||||
const onCancel = () => {
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
};
|
||||
|
||||
function setConfirm() {
|
||||
emitConfirm("update:value", true);
|
||||
showVal.value = false;
|
||||
}
|
||||
const onConfirm = () => {
|
||||
visible.value = false;
|
||||
emits("confirm");
|
||||
};
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
<style scoped>
|
||||
.confirm-div {
|
||||
position: absolute;
|
||||
width: 40vw;
|
||||
|
||||
@@ -4,8 +4,16 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-list class="config-list">
|
||||
<v-list-subheader inset class="config-header">关于</v-list-subheader>
|
||||
<v-list-subheader inset class="config-header">应用信息</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="/tauri.webp" alt="Tauri" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="/icon.webp" alt="App" />
|
||||
@@ -23,14 +31,6 @@
|
||||
<v-list-item-subtitle>{{ versionApp }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="/tauri.webp" alt="Tauri" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="成就版本">
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="../assets/icons/achievements.svg" alt="Achievements" />
|
||||
@@ -42,9 +42,9 @@
|
||||
<v-list-subheader inset class="config-header">设置</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item @click="openMergeData" prepend-icon="mdi-folder" title="打开用户数据目录" />
|
||||
<v-list-item @click="deleteData" prepend-icon="mdi-delete" title="清除用户缓存" />
|
||||
<v-list-item @click="deleteTemp" prepend-icon="mdi-delete" title="清除临时数据" />
|
||||
<v-list-item @click="setDefaultConfig" prepend-icon="mdi-cog" title="恢复默认设置" />
|
||||
<v-list-item @click="tryConfirm('delUser')" prepend-icon="mdi-delete" title="清除用户缓存" />
|
||||
<v-list-item @click="tryConfirm('delTemp')" prepend-icon="mdi-delete" title="清除临时数据" />
|
||||
<v-list-item @click="tryConfirm('delApp')" prepend-icon="mdi-cog" title="恢复默认设置" />
|
||||
<v-list-subheader inset class="config-header">调试</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="开发者模式" subtitle="开启后将显示调试信息">
|
||||
@@ -75,7 +75,8 @@
|
||||
<template v-slot:append>
|
||||
<v-btn @click="submitHome" class="card-btn">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />提交
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />
|
||||
提交
|
||||
</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -96,7 +97,7 @@
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
<!-- 确认弹窗 -->
|
||||
<t-confirm :title="confirmText" v-model:value="confirmValue" ref="confirmRef" />
|
||||
<t-confirm :title="confirmText" v-model="confirmShow" @confirm="doConfirm(confirmOper)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -121,23 +122,25 @@ const appStore = useAppStore();
|
||||
const homeStore = useHomeStore();
|
||||
const achievementsStore = useAchievementsStore();
|
||||
|
||||
// About
|
||||
const loading = ref(true);
|
||||
const versionApp = ref("");
|
||||
const versionTauri = ref("");
|
||||
// About App
|
||||
const versionApp = ref("" as string);
|
||||
const versionTauri = ref("" as string);
|
||||
|
||||
// loading
|
||||
const loading = ref(true as boolean);
|
||||
|
||||
// data
|
||||
const showHome = ref(homeStore.getShowValue());
|
||||
const showHome = ref(homeStore.getShowValue() as string[]);
|
||||
|
||||
// snackbar
|
||||
const snackbar = ref(false);
|
||||
const snackbarText = ref("");
|
||||
const snackbarColor = ref("success");
|
||||
const snackbar = ref(false as boolean);
|
||||
const snackbarText = ref("" as string);
|
||||
const snackbarColor = ref("success" as string);
|
||||
|
||||
// confirm
|
||||
const confirmRef = ref();
|
||||
const confirmText = ref("");
|
||||
const confirmValue = ref(false);
|
||||
const confirmText = ref("" as string);
|
||||
const confirmOper = ref("" as string);
|
||||
const confirmShow = ref(false as boolean);
|
||||
|
||||
// load version
|
||||
onMounted(async () => {
|
||||
@@ -160,48 +163,87 @@ async function openMergeData() {
|
||||
filters: [],
|
||||
});
|
||||
}
|
||||
// 删除本地数据
|
||||
async function deleteData() {
|
||||
confirmText.value = "确定要删除用户数据吗?";
|
||||
confirmRef.value.showConfirm();
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
const res = confirmValue.value;
|
||||
if (res) {
|
||||
await fs.removeDir("userData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
getDataList.map(async item => {
|
||||
await WriteTGData(item.name, item.data);
|
||||
});
|
||||
snackbarText.value = "用户数据已删除!";
|
||||
snackbar.value = true;
|
||||
await achievementsStore.init();
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
|
||||
// open confirm
|
||||
function tryConfirm(oper: string) {
|
||||
switch (oper) {
|
||||
case "delTemp":
|
||||
confirmText.value = "确认清除临时数据吗?";
|
||||
confirmOper.value = "delTemp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delUser":
|
||||
confirmText.value = "确认清除用户缓存吗?";
|
||||
confirmOper.value = "delUser";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delApp":
|
||||
confirmText.value = "确认恢复默认设置吗?";
|
||||
confirmOper.value = "delApp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 删除临时数据
|
||||
async function deleteTemp() {
|
||||
confirmText.value = "确定要删除临时数据吗?";
|
||||
confirmRef.value.showConfirm();
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
const res = confirmValue.value;
|
||||
if (res) {
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
snackbarText.value = "临时数据已删除!";
|
||||
snackbar.value = true;
|
||||
|
||||
// transfer confirm oper
|
||||
function doConfirm(oper: string) {
|
||||
switch (oper) {
|
||||
case "delTemp":
|
||||
delTempData();
|
||||
break;
|
||||
case "delUser":
|
||||
delUserData();
|
||||
break;
|
||||
case "delApp":
|
||||
initAppData();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// confirmOper
|
||||
async function delTempData() {
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
snackbarText.value = "临时数据已删除!";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
async function delUserData() {
|
||||
await fs.removeDir("userData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
getDataList.map(async item => {
|
||||
await WriteTGData(item.name, item.data);
|
||||
});
|
||||
snackbarText.value = "用户数据已删除!";
|
||||
snackbar.value = true;
|
||||
await achievementsStore.init();
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
}
|
||||
|
||||
// 恢复默认配置
|
||||
async function initAppData() {
|
||||
await appStore.init();
|
||||
await homeStore.init();
|
||||
await achievementsStore.init();
|
||||
snackbarText.value = "已恢复默认配置!";
|
||||
snackbar.value = true;
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// 开启 dev 模式
|
||||
async function submitDevMode() {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
@@ -228,24 +270,6 @@ async function submitHome() {
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
// 恢复默认配置
|
||||
async function setDefaultConfig() {
|
||||
confirmText.value = "确定要恢复默认配置吗?";
|
||||
confirmRef.value.showConfirm();
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
const res = confirmValue.value;
|
||||
if (res) {
|
||||
await appStore.init();
|
||||
await homeStore.init();
|
||||
await achievementsStore.init();
|
||||
snackbarText.value = "已恢复默认配置!";
|
||||
snackbar.value = true;
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,685 +0,0 @@
|
||||
/**
|
||||
* SVGInject - Version 1.2.3
|
||||
* A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
|
||||
*
|
||||
* https://github.com/iconfu/svg-inject
|
||||
*
|
||||
* Copyright (c) 2018 INCORS, the creators of iconfu.com
|
||||
* @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE
|
||||
*/
|
||||
(function (window, document) {
|
||||
// constants for better minification
|
||||
var _CREATE_ELEMENT_ = "createElement";
|
||||
var _GET_ELEMENTS_BY_TAG_NAME_ = "getElementsByTagName";
|
||||
var _LENGTH_ = "length";
|
||||
var _STYLE_ = "style";
|
||||
var _TITLE_ = "title";
|
||||
var _UNDEFINED_ = "undefined";
|
||||
var _SET_ATTRIBUTE_ = "setAttribute";
|
||||
var _GET_ATTRIBUTE_ = "getAttribute";
|
||||
|
||||
var NULL = null;
|
||||
|
||||
// constants
|
||||
var __SVGINJECT = "__svgInject";
|
||||
var ID_SUFFIX = "--inject-";
|
||||
var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + "\\d+", "g");
|
||||
var LOAD_FAIL = "LOAD_FAIL";
|
||||
var SVG_NOT_SUPPORTED = "SVG_NOT_SUPPORTED";
|
||||
var SVG_INVALID = "SVG_INVALID";
|
||||
var ATTRIBUTE_EXCLUSION_NAMES = ["src", "alt", "onload", "onerror"];
|
||||
var A_ELEMENT = document[_CREATE_ELEMENT_]("a");
|
||||
var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_;
|
||||
var DEFAULT_OPTIONS = {
|
||||
useCache: true,
|
||||
copyAttributes: true,
|
||||
makeIdsUnique: true,
|
||||
};
|
||||
// Map of IRI referenceable tag names to properties that can reference them. This is defined in
|
||||
// https://www.w3.org/TR/SVG11/linking.html#processingIRI
|
||||
var IRI_TAG_PROPERTIES_MAP = {
|
||||
clipPath: ["clip-path"],
|
||||
"color-profile": NULL,
|
||||
cursor: NULL,
|
||||
filter: NULL,
|
||||
linearGradient: ["fill", "stroke"],
|
||||
marker: ["marker", "marker-end", "marker-mid", "marker-start"],
|
||||
mask: NULL,
|
||||
pattern: ["fill", "stroke"],
|
||||
radialGradient: ["fill", "stroke"],
|
||||
};
|
||||
var INJECTED = 1;
|
||||
var FAIL = 2;
|
||||
|
||||
var uniqueIdCounter = 1;
|
||||
var xmlSerializer;
|
||||
var domParser;
|
||||
|
||||
// creates an SVG document from an SVG string
|
||||
function svgStringToSvgDoc(svgStr) {
|
||||
domParser = domParser || new DOMParser();
|
||||
return domParser.parseFromString(svgStr, "text/xml");
|
||||
}
|
||||
|
||||
// searializes an SVG element to an SVG string
|
||||
function svgElemToSvgString(svgElement) {
|
||||
xmlSerializer = xmlSerializer || new XMLSerializer();
|
||||
return xmlSerializer.serializeToString(svgElement);
|
||||
}
|
||||
|
||||
// Returns the absolute url for the specified url
|
||||
function getAbsoluteUrl(url) {
|
||||
A_ELEMENT.href = url;
|
||||
return A_ELEMENT.href;
|
||||
}
|
||||
|
||||
// Load svg with an XHR request
|
||||
function loadSvg(url, callback, errorCallback) {
|
||||
if (url) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
// readyState is DONE
|
||||
var status = req.status;
|
||||
if (status == 200) {
|
||||
// request status is OK
|
||||
callback(req.responseXML, req.responseText.trim());
|
||||
} else if (status >= 400) {
|
||||
// request status is error (4xx or 5xx)
|
||||
errorCallback();
|
||||
} else if (status == 0) {
|
||||
// request status 0 can indicate a failed cross-domain call
|
||||
errorCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
req.open("GET", url, true);
|
||||
req.send();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy attributes from img element to svg element
|
||||
function copyAttributes(imgElem, svgElem) {
|
||||
var attribute;
|
||||
var attributeName;
|
||||
var attributeValue;
|
||||
var attributes = imgElem.attributes;
|
||||
for (var i = 0; i < attributes[_LENGTH_]; i++) {
|
||||
attribute = attributes[i];
|
||||
attributeName = attribute.name;
|
||||
// Only copy attributes not explicitly excluded from copying
|
||||
if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) {
|
||||
attributeValue = attribute.value;
|
||||
// If img attribute is "title", insert a title element into SVG element
|
||||
if (attributeName == _TITLE_) {
|
||||
var titleElem;
|
||||
var firstElementChild = svgElem.firstElementChild;
|
||||
if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) {
|
||||
// If the SVG element's first child is a title element, keep it as the title element
|
||||
titleElem = firstElementChild;
|
||||
} else {
|
||||
// If the SVG element's first child element is not a title element, create a new title
|
||||
// ele,emt and set it as the first child
|
||||
titleElem = document[_CREATE_ELEMENT_ + "NS"]("http://www.w3.org/2000/svg", _TITLE_);
|
||||
svgElem.insertBefore(titleElem, firstElementChild);
|
||||
}
|
||||
// Set new title content
|
||||
titleElem.textContent = attributeValue;
|
||||
} else {
|
||||
// Set img attribute to svg element
|
||||
svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision
|
||||
// between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is
|
||||
// incremented with each injection. References to the IDs are adjusted accordingly.
|
||||
// We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one
|
||||
// injected SVG.
|
||||
// If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG
|
||||
function makeIdsUnique(svgElem, onlyReferenced) {
|
||||
var idSuffix = ID_SUFFIX + uniqueIdCounter++;
|
||||
// Regular expression for functional notations of an IRI references. This will find occurences in the form
|
||||
// url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID
|
||||
var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g;
|
||||
// Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but
|
||||
// this is not a requirement, therefore we have to search for IDs in the whole SVG.
|
||||
var idElements = svgElem.querySelectorAll("[id]");
|
||||
var idElem;
|
||||
// An object containing referenced IDs as keys is used if only referenced IDs should be uniquified.
|
||||
// If this object does not exist, all IDs will be uniquified.
|
||||
var referencedIds = onlyReferenced ? [] : NULL;
|
||||
var tagName;
|
||||
var iriTagNames = {};
|
||||
var iriProperties = [];
|
||||
var changed = false;
|
||||
var i, j;
|
||||
|
||||
if (idElements[_LENGTH_]) {
|
||||
// Make all IDs unique by adding the ID suffix and collect all encountered tag names
|
||||
// that are IRI referenceable from properities.
|
||||
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||||
tagName = idElements[i].localName; // Use non-namespaced tag name
|
||||
// Make ID unique if tag name is IRI referenceable
|
||||
if (tagName in IRI_TAG_PROPERTIES_MAP) {
|
||||
iriTagNames[tagName] = 1;
|
||||
}
|
||||
}
|
||||
// Get all properties that are mapped to the found IRI referenceable tags
|
||||
for (tagName in iriTagNames) {
|
||||
(IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) {
|
||||
// Add mapped properties to array of iri referencing properties.
|
||||
// Use linear search here because the number of possible entries is very small (maximum 11)
|
||||
if (iriProperties.indexOf(mappedProperty) < 0) {
|
||||
iriProperties.push(mappedProperty);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (iriProperties[_LENGTH_]) {
|
||||
// Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"'
|
||||
iriProperties.push(_STYLE_);
|
||||
}
|
||||
// Run through all elements of the SVG and replace IDs in references.
|
||||
// To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*').
|
||||
// Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately.
|
||||
var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]("*");
|
||||
var element = svgElem;
|
||||
var propertyName;
|
||||
var value;
|
||||
var newValue;
|
||||
for (i = -1; element != NULL; ) {
|
||||
if (element.localName == _STYLE_) {
|
||||
// If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content
|
||||
value = element.textContent;
|
||||
newValue =
|
||||
value &&
|
||||
value.replace(funcIriRegex, function (match, id) {
|
||||
if (referencedIds) {
|
||||
referencedIds[id] = 1;
|
||||
}
|
||||
return "url(#" + id + idSuffix + ")";
|
||||
});
|
||||
if (newValue !== value) {
|
||||
element.textContent = newValue;
|
||||
}
|
||||
} else if (element.hasAttributes()) {
|
||||
// Run through all property names for which IDs were found
|
||||
for (j = 0; j < iriProperties[_LENGTH_]; j++) {
|
||||
propertyName = iriProperties[j];
|
||||
value = element[_GET_ATTRIBUTE_](propertyName);
|
||||
newValue =
|
||||
value &&
|
||||
value.replace(funcIriRegex, function (match, id) {
|
||||
if (referencedIds) {
|
||||
referencedIds[id] = 1;
|
||||
}
|
||||
return "url(#" + id + idSuffix + ")";
|
||||
});
|
||||
if (newValue !== value) {
|
||||
element[_SET_ATTRIBUTE_](propertyName, newValue);
|
||||
}
|
||||
}
|
||||
// Replace IDs in xlink:ref and href attributes
|
||||
["xlink:href", "href"].forEach(function (refAttrName) {
|
||||
var iri = element[_GET_ATTRIBUTE_](refAttrName);
|
||||
if (/^\s*#/.test(iri)) {
|
||||
// Check if iri is non-null and internal reference
|
||||
iri = iri.trim();
|
||||
element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix);
|
||||
if (referencedIds) {
|
||||
// Add ID to referenced IDs
|
||||
referencedIds[iri.substring(1)] = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
element = descElements[++i];
|
||||
}
|
||||
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||||
idElem = idElements[i];
|
||||
// If set of referenced IDs exists, make only referenced IDs unique,
|
||||
// otherwise make all IDs unique.
|
||||
if (!referencedIds || referencedIds[idElem.id]) {
|
||||
// Add suffix to element's ID
|
||||
idElem.id += idSuffix;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// return true if SVG element has changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
// For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a
|
||||
// higher ID counter. This is much more performant than a call to makeIdsUnique().
|
||||
function makeIdsUniqueCached(svgString) {
|
||||
return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++);
|
||||
}
|
||||
|
||||
// Inject SVG by replacing the img element with the SVG element in the DOM
|
||||
function inject(imgElem, svgElem, absUrl, options) {
|
||||
if (svgElem) {
|
||||
svgElem[_SET_ATTRIBUTE_]("data-inject-url", absUrl);
|
||||
var parentNode = imgElem.parentNode;
|
||||
if (parentNode) {
|
||||
if (options.copyAttributes) {
|
||||
copyAttributes(imgElem, svgElem);
|
||||
}
|
||||
// Invoke beforeInject hook if set
|
||||
var beforeInject = options.beforeInject;
|
||||
var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem;
|
||||
// Replace img element with new element. This is the actual injection.
|
||||
parentNode.replaceChild(injectElem, imgElem);
|
||||
// Mark img element as injected
|
||||
imgElem[__SVGINJECT] = INJECTED;
|
||||
removeOnLoadAttribute(imgElem);
|
||||
// Invoke afterInject hook if set
|
||||
var afterInject = options.afterInject;
|
||||
if (afterInject) {
|
||||
afterInject(imgElem, injectElem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
svgInvalid(imgElem, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Merges any number of options objects into a new object
|
||||
function mergeOptions() {
|
||||
var mergedOptions = {};
|
||||
var args = arguments;
|
||||
// Iterate over all specified options objects and add all properties to the new options object
|
||||
for (var i = 0; i < args[_LENGTH_]; i++) {
|
||||
var argument = args[i];
|
||||
for (var key in argument) {
|
||||
if (argument.hasOwnProperty(key)) {
|
||||
mergedOptions[key] = argument[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergedOptions;
|
||||
}
|
||||
|
||||
// Adds the specified CSS to the document's <head> element
|
||||
function addStyleToHead(css) {
|
||||
var head = document[_GET_ELEMENTS_BY_TAG_NAME_]("head")[0];
|
||||
if (head) {
|
||||
var style = document[_CREATE_ELEMENT_](_STYLE_);
|
||||
style.type = "text/css";
|
||||
style.appendChild(document.createTextNode(css));
|
||||
head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
// Builds an SVG element from the specified SVG string
|
||||
function buildSvgElement(svgStr, verify) {
|
||||
if (verify) {
|
||||
var svgDoc;
|
||||
try {
|
||||
// Parse the SVG string with DOMParser
|
||||
svgDoc = svgStringToSvgDoc(svgStr);
|
||||
} catch (e) {
|
||||
return NULL;
|
||||
}
|
||||
if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]("parsererror")[_LENGTH_]) {
|
||||
// DOMParser does not throw an exception, but instead puts parsererror tags in the document
|
||||
return NULL;
|
||||
}
|
||||
return svgDoc.documentElement;
|
||||
} else {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = svgStr;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
function removeOnLoadAttribute(imgElem) {
|
||||
// Remove the onload attribute. Should only be used to remove the unstyled image flash protection and
|
||||
// make the element visible, not for removing the event listener.
|
||||
imgElem.removeAttribute("onload");
|
||||
}
|
||||
|
||||
function errorMessage(msg) {
|
||||
console.error("SVGInject: " + msg);
|
||||
}
|
||||
|
||||
function fail(imgElem, status, options) {
|
||||
imgElem[__SVGINJECT] = FAIL;
|
||||
if (options.onFail) {
|
||||
options.onFail(imgElem, status);
|
||||
} else {
|
||||
errorMessage(status);
|
||||
}
|
||||
}
|
||||
|
||||
function svgInvalid(imgElem, options) {
|
||||
removeOnLoadAttribute(imgElem);
|
||||
fail(imgElem, SVG_INVALID, options);
|
||||
}
|
||||
|
||||
function svgNotSupported(imgElem, options) {
|
||||
removeOnLoadAttribute(imgElem);
|
||||
fail(imgElem, SVG_NOT_SUPPORTED, options);
|
||||
}
|
||||
|
||||
function loadFail(imgElem, options) {
|
||||
fail(imgElem, LOAD_FAIL, options);
|
||||
}
|
||||
|
||||
function removeEventListeners(imgElem) {
|
||||
imgElem.onload = NULL;
|
||||
imgElem.onerror = NULL;
|
||||
}
|
||||
|
||||
function imgNotSet(msg) {
|
||||
errorMessage("no img element");
|
||||
}
|
||||
|
||||
function createSVGInject(globalName, options) {
|
||||
var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options);
|
||||
var svgLoadCache = {};
|
||||
|
||||
if (IS_SVG_SUPPORTED) {
|
||||
// If the browser supports SVG, add a small stylesheet that hides the <img> elements until
|
||||
// injection is finished. This avoids showing the unstyled SVGs before style is applied.
|
||||
addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}');
|
||||
}
|
||||
|
||||
/**
|
||||
* SVGInject
|
||||
*
|
||||
* Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img`
|
||||
* elements. Returns a Promise object which resolves if all passed in `img` elements have either been
|
||||
* injected or failed to inject (Only if a global Promise object is available like in all modern browsers
|
||||
* or through a polyfill).
|
||||
*
|
||||
* Options:
|
||||
* useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`.
|
||||
* copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value
|
||||
* is `true`.
|
||||
* makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by
|
||||
* property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a
|
||||
* running number which increases with each injection. This is done to avoid duplicate IDs in the DOM.
|
||||
* beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns
|
||||
* a string it is used as the URL instead of the `img` element's `src` attribute.
|
||||
* afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a
|
||||
* parameters. If caching is active this hook will only get called once for injected SVGs with the
|
||||
* same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs
|
||||
* with the same absolute path. It's also possible to return an `svg` string or `svg` element which
|
||||
* will then be used for the injection.
|
||||
* beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If
|
||||
* any html element is returned it gets injected instead of applying the default SVG injection.
|
||||
* afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters.
|
||||
* onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or
|
||||
* failed to inject.
|
||||
* onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter.
|
||||
* The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG),
|
||||
* `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed).
|
||||
*
|
||||
* @param {HTMLImageElement} img - an img element or an array of img elements
|
||||
* @param {Object} [options] - optional parameter with [options](#options) for this injection.
|
||||
*/
|
||||
function SVGInject(img, options) {
|
||||
options = mergeOptions(defaultOptions, options);
|
||||
|
||||
var run = function (resolve) {
|
||||
var allFinish = function () {
|
||||
var onAllFinish = options.onAllFinish;
|
||||
if (onAllFinish) {
|
||||
onAllFinish();
|
||||
}
|
||||
resolve && resolve();
|
||||
};
|
||||
|
||||
if (img && typeof img[_LENGTH_] != _UNDEFINED_) {
|
||||
// an array like structure of img elements
|
||||
var injectIndex = 0;
|
||||
var injectCount = img[_LENGTH_];
|
||||
|
||||
if (injectCount == 0) {
|
||||
allFinish();
|
||||
} else {
|
||||
var finish = function () {
|
||||
if (++injectIndex == injectCount) {
|
||||
allFinish();
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < injectCount; i++) {
|
||||
SVGInjectElement(img[i], options, finish);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// only one img element
|
||||
SVGInjectElement(img, options, allFinish);
|
||||
}
|
||||
};
|
||||
|
||||
// return a Promise object if globally available
|
||||
return typeof Promise == _UNDEFINED_ ? run() : new Promise(run);
|
||||
}
|
||||
|
||||
// Injects a single svg element. Options must be already merged with the default options.
|
||||
function SVGInjectElement(imgElem, options, callback) {
|
||||
if (imgElem) {
|
||||
var svgInjectAttributeValue = imgElem[__SVGINJECT];
|
||||
if (!svgInjectAttributeValue) {
|
||||
removeEventListeners(imgElem);
|
||||
|
||||
if (!IS_SVG_SUPPORTED) {
|
||||
svgNotSupported(imgElem, options);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load
|
||||
// URL path. Else use the imgElem's src attribute value.
|
||||
var beforeLoad = options.beforeLoad;
|
||||
var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]("src");
|
||||
|
||||
if (!src) {
|
||||
// If no image src attribute is set do no injection. This can only be reached by using javascript
|
||||
// because if no src attribute is set the onload and onerror events do not get called
|
||||
if (src === "") {
|
||||
loadFail(imgElem, options);
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// set array so later calls can register callbacks
|
||||
var onFinishCallbacks = [];
|
||||
imgElem[__SVGINJECT] = onFinishCallbacks;
|
||||
|
||||
var onFinish = function () {
|
||||
callback();
|
||||
onFinishCallbacks.forEach(function (onFinishCallback) {
|
||||
onFinishCallback();
|
||||
});
|
||||
};
|
||||
|
||||
var absUrl = getAbsoluteUrl(src);
|
||||
var useCacheOption = options.useCache;
|
||||
var makeIdsUniqueOption = options.makeIdsUnique;
|
||||
|
||||
var setSvgLoadCacheValue = function (val) {
|
||||
if (useCacheOption) {
|
||||
svgLoadCache[absUrl].forEach(function (svgLoad) {
|
||||
svgLoad(val);
|
||||
});
|
||||
svgLoadCache[absUrl] = val;
|
||||
}
|
||||
};
|
||||
|
||||
if (useCacheOption) {
|
||||
var svgLoad = svgLoadCache[absUrl];
|
||||
|
||||
var handleLoadValue = function (loadValue) {
|
||||
if (loadValue === LOAD_FAIL) {
|
||||
loadFail(imgElem, options);
|
||||
} else if (loadValue === SVG_INVALID) {
|
||||
svgInvalid(imgElem, options);
|
||||
} else {
|
||||
var hasUniqueIds = loadValue[0];
|
||||
var svgString = loadValue[1];
|
||||
var uniqueIdsSvgString = loadValue[2];
|
||||
var svgElem;
|
||||
|
||||
if (makeIdsUniqueOption) {
|
||||
if (hasUniqueIds === NULL) {
|
||||
// IDs for the SVG string have not been made unique before. This may happen if previous
|
||||
// injection of a cached SVG have been run with the option makedIdsUnique set to false
|
||||
svgElem = buildSvgElement(svgString, false);
|
||||
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||||
|
||||
loadValue[0] = hasUniqueIds;
|
||||
loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem);
|
||||
} else if (hasUniqueIds) {
|
||||
// Make IDs unique for already cached SVGs with better performance
|
||||
svgString = makeIdsUniqueCached(uniqueIdsSvgString);
|
||||
}
|
||||
}
|
||||
|
||||
svgElem = svgElem || buildSvgElement(svgString, false);
|
||||
|
||||
inject(imgElem, svgElem, absUrl, options);
|
||||
}
|
||||
onFinish();
|
||||
};
|
||||
|
||||
if (typeof svgLoad != _UNDEFINED_) {
|
||||
// Value for url exists in cache
|
||||
if (svgLoad.isCallbackQueue) {
|
||||
// Same url has been cached, but value has not been loaded yet, so add to callbacks
|
||||
svgLoad.push(handleLoadValue);
|
||||
} else {
|
||||
handleLoadValue(svgLoad);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
var svgLoad = [];
|
||||
// set property isCallbackQueue to Array to differentiate from array with cached loaded values
|
||||
svgLoad.isCallbackQueue = true;
|
||||
svgLoadCache[absUrl] = svgLoad;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the SVG because it is not cached or caching is disabled
|
||||
loadSvg(
|
||||
absUrl,
|
||||
function (svgXml, svgString) {
|
||||
// Use the XML from the XHR request if it is an instance of Document. Otherwise
|
||||
// (for example of IE9), create the svg document from the svg string.
|
||||
var svgElem =
|
||||
svgXml instanceof Document
|
||||
? svgXml.documentElement
|
||||
: buildSvgElement(svgString, true);
|
||||
|
||||
var afterLoad = options.afterLoad;
|
||||
if (afterLoad) {
|
||||
// Invoke afterLoad hook which may modify the SVG element. After load may also return a new
|
||||
// svg element or svg string
|
||||
var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem;
|
||||
if (svgElemOrSvgString) {
|
||||
// Update svgElem and svgString because of modifications to the SVG element or SVG string in
|
||||
// the afterLoad hook, so the modified SVG is also used for all later cached injections
|
||||
var isString = typeof svgElemOrSvgString == "string";
|
||||
svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem);
|
||||
svgElem = isString
|
||||
? buildSvgElement(svgElemOrSvgString, true)
|
||||
: svgElemOrSvgString;
|
||||
}
|
||||
}
|
||||
|
||||
if (svgElem instanceof SVGElement) {
|
||||
var hasUniqueIds = NULL;
|
||||
if (makeIdsUniqueOption) {
|
||||
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||||
}
|
||||
|
||||
if (useCacheOption) {
|
||||
var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem);
|
||||
// set an array with three entries to the load cache
|
||||
setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]);
|
||||
}
|
||||
|
||||
inject(imgElem, svgElem, absUrl, options);
|
||||
} else {
|
||||
svgInvalid(imgElem, options);
|
||||
setSvgLoadCacheValue(SVG_INVALID);
|
||||
}
|
||||
onFinish();
|
||||
},
|
||||
function () {
|
||||
loadFail(imgElem, options);
|
||||
setSvgLoadCacheValue(LOAD_FAIL);
|
||||
onFinish();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (Array.isArray(svgInjectAttributeValue)) {
|
||||
// svgInjectAttributeValue is an array. Injection is not complete so register callback
|
||||
svgInjectAttributeValue.push(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgNotSet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default [options](#options) for SVGInject.
|
||||
*
|
||||
* @param {Object} [options] - default [options](#options) for an injection.
|
||||
*/
|
||||
SVGInject.setOptions = function (options) {
|
||||
defaultOptions = mergeOptions(defaultOptions, options);
|
||||
};
|
||||
|
||||
// Create a new instance of SVGInject
|
||||
SVGInject.create = createSVGInject;
|
||||
|
||||
/**
|
||||
* Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails
|
||||
* (for example if file is not found or if the browser does not support SVG). This triggers a call to the
|
||||
* options onFail hook if available. The optional second parameter will be set as the new src attribute
|
||||
* for the img element.
|
||||
*
|
||||
* @param {HTMLImageElement} img - an img element
|
||||
* @param {String} [fallbackSrc] - optional parameter fallback src
|
||||
*/
|
||||
SVGInject.err = function (img, fallbackSrc) {
|
||||
if (img) {
|
||||
if (img[__SVGINJECT] != FAIL) {
|
||||
removeEventListeners(img);
|
||||
|
||||
if (!IS_SVG_SUPPORTED) {
|
||||
svgNotSupported(img, defaultOptions);
|
||||
} else {
|
||||
removeOnLoadAttribute(img);
|
||||
loadFail(img, defaultOptions);
|
||||
}
|
||||
if (fallbackSrc) {
|
||||
removeOnLoadAttribute(img);
|
||||
img.src = fallbackSrc;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgNotSet();
|
||||
}
|
||||
};
|
||||
|
||||
window[globalName] = SVGInject;
|
||||
|
||||
return SVGInject;
|
||||
}
|
||||
|
||||
var SVGInjectInstance = createSVGInject("SVGInject");
|
||||
|
||||
if (typeof module == "object" && typeof module.exports == "object") {
|
||||
module.exports = SVGInjectInstance;
|
||||
}
|
||||
})(window, document);
|
||||
Reference in New Issue
Block a user