liushengqiang 2 anni fa
parent
commit
cb827fc0a9

+ 1 - 0
src/page-instrument/component/mode-type-mode/index.module.less

@@ -13,6 +13,7 @@
   transition: all .3s ease-in-out;
   img {
     width: 24%;
+    max-width: 220Px;
   }
 }
 .hidden{

+ 17 - 14
src/page-instrument/follow-model/index.tsx

@@ -2,28 +2,20 @@ import { Transition, defineComponent, onBeforeUnmount, onMounted, ref } from "vu
 import styles from "./index.module.less";
 import icons from "./icons.json";
 import { followData, handleFollowEnd, handleFollowStart } from "/src/view/follow-practice";
+import { Popup } from "vant";
+import Microphone from "./microphone";
 
 export default defineComponent({
 	name: "follow-model",
 	setup() {
-		const startBtn = ref(false);
-		const endBtn = ref(false);
-		onMounted(() => {
-			startBtn.value = true;
-		});
-		onBeforeUnmount(() => {
-			startBtn.value = false;
-		});
 		return () => (
 			<>
 				<Transition name="pop-center">
-					{startBtn.value && (
+					{!followData.start && (
 						<div class={styles.startBtn} key="start">
 							<img
 								src={icons.start}
 								onClick={() => {
-									startBtn.value = false;
-									endBtn.value = true;
 									handleFollowStart();
 								}}
 							/>
@@ -31,13 +23,11 @@ export default defineComponent({
 					)}
 				</Transition>
 				<Transition name="pop-center">
-					{endBtn.value && (
+					{followData.start && (
 						<div class={styles.endBtn} key="end">
 							<img
 								src={icons.end}
 								onClick={() => {
-									startBtn.value = true;
-									endBtn.value = false;
 									handleFollowEnd();
 								}}
 							/>
@@ -50,6 +40,19 @@ export default defineComponent({
 					<span style={{ background: "rgb(255, 0, 0)" }} class={styles.dot}></span>
 					<span>高</span>
 				</div>
+				<Popup
+					teleport="body"
+					closeOnClickOverlay={false}
+					class={["popup-custom", "van-scale"]}
+					transition="van-scale"
+					v-model:show={followData.earphone}
+				>
+					<Microphone
+						onClose={() => {
+							followData.earphone = false;
+						}}
+					/>
+				</Popup>
 			</>
 		);
 	},

+ 53 - 0
src/page-instrument/follow-model/microphone/images/icon_cancel.svg

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="117px" height="37px" viewBox="0 0 117 37" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>button-normal备份</title>
+    <defs>
+        <linearGradient x1="50%" y1="2.13785238%" x2="50%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#E9E9E9" offset="0%"></stop>
+            <stop stop-color="#C9C9C9" offset="100%"></stop>
+        </linearGradient>
+        <path d="M0,17 C0,7.61115925 7.60904153,0 16.9971663,0 L100.002834,0 C109.390109,0 117,7.60909491 117,17 L117,17 C117,26.3888407 109.390958,34 100.002834,34 L16.9971663,34 C7.60989058,34 0,26.3909051 0,17 L0,17 Z" id="path-2"></path>
+        <filter x="-0.9%" y="-2.9%" width="101.7%" height="111.8%" filterUnits="objectBoundingBox" id="filter-3">
+            <feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feColorMatrix values="0 0 0 0 0.804223987   0 0 0 0 0.804223987   0 0 0 0 0.804223987  0 0 0 1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
+        </filter>
+        <filter x="-1.7%" y="-5.9%" width="103.4%" height="117.6%" filterUnits="objectBoundingBox" id="filter-4">
+            <feGaussianBlur stdDeviation="1.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
+            <feOffset dx="0" dy="1" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
+            <feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
+            <feColorMatrix values="0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.5 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
+        </filter>
+        <text id="text-5" font-family="STYuanti-SC-Bold, Yuanti SC" font-size="16" font-weight="bold" letter-spacing="0.914285714" fill="#FFFFFF">
+            <tspan x="33.1285714" y="22">不允许</tspan>
+        </text>
+        <filter x="-7.9%" y="-18.2%" width="115.9%" height="145.5%" filterUnits="objectBoundingBox" id="filter-6">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.510962887   0 0 0 0 0.510962887   0 0 0 0 0.510962887  0 0 0 0.597490644 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="弹框备份-6" transform="translate(-284.000000, -264.000000)">
+            <g id="编组-16" transform="translate(264.000000, 30.000000)">
+                <g id="弹窗备份" transform="translate(8.000000, 1.158910)">
+                    <g id="button-normal备份" transform="translate(12.000000, 233.749827)">
+                        <g id="button-normal">
+                            <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
+                            <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
+                            <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        </g>
+                        <path d="M5.29281147,8.58018897 C7.4390966,13.9921969 10.1571046,16.6982008 13.4468356,16.6982008 C18.2676985,16.6982008 95.1625096,16.6982008 101.90185,16.6982008 C106.394743,16.6982008 109.960365,13.7988005 112.598716,8 C113.992892,13.5751446 114.565408,17.2438095 114.316264,19.0059945 C114.316264,19.0059945 114.316264,19.0059945 114.316264,19.0059945 C113.296432,26.2192179 107.122625,31.58155 99.8376644,31.5815344 L11.3446737,31.5815344 C5.11489123,25.4571796 2,20.6627351 2,17.1982008 C2,13.7336665 3.09760382,10.8609959 5.29281147,8.58018897 Z" id="矩形" fill="#EBEBEB" opacity="0.499798729"></path>
+                        <g id="编组-6" opacity="0.85" transform="translate(7.706999, 10.293001) rotate(-5.000000) translate(-7.706999, -10.293001) translate(1.477345, 3.038666)" fill="#FFFFFF">
+                            <ellipse id="椭圆形" opacity="0.882866269" transform="translate(7.522655, 4.961334) rotate(44.000000) translate(-7.522655, -4.961334) " cx="7.52265471" cy="4.96133431" rx="3" ry="4"></ellipse>
+                            <ellipse id="椭圆形备份-3" opacity="0.882866269" transform="translate(3.175326, 11.321004) rotate(44.000000) translate(-3.175326, -11.321004) " cx="3.17532553" cy="11.3210042" rx="2" ry="2.5"></ellipse>
+                        </g>
+                        <g id="不允许" fill="#FFFFFF" fill-opacity="1">
+                            <use filter="url(#filter-6)" xlink:href="#text-5"></use>
+                            <use xlink:href="#text-5"></use>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 53 - 0
src/page-instrument/follow-model/microphone/images/icon_confirm.svg

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="117px" height="37px" viewBox="0 0 117 37" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>button-normal备份 4</title>
+    <defs>
+        <linearGradient x1="47.3839597%" y1="0%" x2="47.3839597%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#5BECFF" offset="0%"></stop>
+            <stop stop-color="#259CFE" offset="100%"></stop>
+        </linearGradient>
+        <path d="M0,17 C0,7.61115925 7.60904153,0 16.9971663,0 L100.002834,0 C109.390109,0 117,7.60909491 117,17 L117,17 C117,26.3888407 109.390958,34 100.002834,34 L16.9971663,34 C7.60989058,34 0,26.3909051 0,17 L0,17 Z" id="path-2"></path>
+        <filter x="-0.9%" y="-2.9%" width="101.7%" height="111.8%" filterUnits="objectBoundingBox" id="filter-3">
+            <feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feColorMatrix values="0 0 0 0 0.140022123   0 0 0 0 0.580018696   0 0 0 0 0.741724871  0 0 0 1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
+        </filter>
+        <filter x="-1.7%" y="-5.9%" width="103.4%" height="117.6%" filterUnits="objectBoundingBox" id="filter-4">
+            <feGaussianBlur stdDeviation="1.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
+            <feOffset dx="0" dy="1" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
+            <feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
+            <feColorMatrix values="0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.5 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
+        </filter>
+        <text id="text-5" font-family="STYuanti-SC-Bold, Yuanti SC" font-size="16" font-weight="bold" letter-spacing="0.914285714" fill="#FFFFFF">
+            <tspan x="33.1285714" y="22">去开启</tspan>
+        </text>
+        <filter x="-3.2%" y="-4.5%" width="106.3%" height="118.2%" filterUnits="objectBoundingBox" id="filter-6">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.161975631   0 0 0 0 0.583690214   0 0 0 0 0.702658833  0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="弹框备份-6" transform="translate(-407.000000, -264.000000)">
+            <g id="编组-16" transform="translate(264.000000, 30.000000)">
+                <g id="弹窗备份" transform="translate(8.000000, 1.158910)">
+                    <g id="button-normal备份-4" transform="translate(135.000000, 233.749827)">
+                        <g id="button-normal">
+                            <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
+                            <use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
+                            <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        </g>
+                        <path d="M5.29281147,8.58018897 C7.4390966,13.9921969 10.1571046,16.6982008 13.4468356,16.6982008 C18.2676985,16.6982008 95.1625096,16.6982008 101.90185,16.6982008 C106.394743,16.6982008 109.960365,13.7988005 112.598716,8 C113.992892,13.5751446 114.565408,17.2438095 114.316264,19.0059945 C114.316264,19.0059945 114.316264,19.0059945 114.316264,19.0059945 C113.296432,26.2192179 107.122625,31.58155 99.8376644,31.5815344 L11.3446737,31.5815344 C5.11489123,25.4571796 2,20.6627351 2,17.1982008 C2,13.7336665 3.09760382,10.8609959 5.29281147,8.58018897 Z" id="矩形" fill="#1CACF1" opacity="0.499798729"></path>
+                        <g id="编组-6" opacity="0.85" transform="translate(7.706999, 10.293001) rotate(-5.000000) translate(-7.706999, -10.293001) translate(1.477345, 3.038666)" fill="#FFFFFF">
+                            <ellipse id="椭圆形" opacity="0.882866269" transform="translate(7.522655, 4.961334) rotate(44.000000) translate(-7.522655, -4.961334) " cx="7.52265471" cy="4.96133431" rx="3" ry="4"></ellipse>
+                            <ellipse id="椭圆形备份-3" opacity="0.882866269" transform="translate(3.175326, 11.321004) rotate(44.000000) translate(-3.175326, -11.321004) " cx="3.17532553" cy="11.3210042" rx="2" ry="2.5"></ellipse>
+                        </g>
+                        <g id="去开启" fill="#FFFFFF" fill-opacity="1">
+                            <use filter="url(#filter-6)" xlink:href="#text-5"></use>
+                            <use xlink:href="#text-5"></use>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/page-instrument/follow-model/microphone/images/index.json


+ 45 - 0
src/page-instrument/follow-model/microphone/index.module.less

@@ -0,0 +1,45 @@
+.fraction {
+    color: #fff;
+    border-radius: 18px;
+    width: 264px;
+    overflow: hidden;
+}
+.content{
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+    background-color: #fff;
+    top: -2px;
+}
+.title {
+    color: #1A1A1A;
+    font-size: 17px;
+    padding: 16px 0 10px 0;
+    font-weight: bold;
+}
+
+.erji {
+    position: relative;
+    width: 100%;
+    display: block;
+    z-index: 10;
+}
+
+.tip {
+    font-size: 13px;
+    color: #808080;
+}
+
+.btns{
+    display: flex;
+    align-items: center;
+}
+
+.btn {
+    display: block;
+    width: 117px;
+    height: 36px;
+    margin: 18px 0 17px 0;
+}

+ 25 - 0
src/page-instrument/follow-model/microphone/index.tsx

@@ -0,0 +1,25 @@
+import { defineComponent } from "vue";
+import styles from "./index.module.less";
+import icons from "./images/index.json";
+import icon_cancel from "./images/icon_cancel.svg";
+import icon_confirm from "./images/icon_confirm.svg";
+
+export default defineComponent({
+	name: "earphone",
+	emits: ["close"],
+	setup(props, { emit }) {
+		return () => (
+			<div class={styles.fraction}>
+				<img class={styles.erji} src={icons.title} />
+				<div class={styles.content}>
+					<div class={styles.title}>开启权限</div>
+					<div class={styles.tip}>请开启麦克风访问权限</div>
+					<div class={styles.btns}>
+						<img src={icon_cancel} class={styles.btn} onClick={() => emit("close")} />
+						<img src={icon_confirm} class={styles.btn} onClick={() => emit("close")} />
+					</div>
+				</div>
+			</div>
+		);
+	},
+});

+ 138 - 0
src/view/follow-practice/audioRecorder.ts

@@ -0,0 +1,138 @@
+/** 频率自相关算法
+ * 参考: https://github.com/cwilso/PitchDetect
+ * @param {Float32Array} buf - 音频数据
+ * @param {number} sampleRate - 采样率
+ * @returns {number} 频率
+ */
+function autoCorrelate(buf: Float32Array, sampleRate: number) {
+	// Implements the ACF2+ algorithm
+	var SIZE = buf.length;
+	var rms = 0;
+
+	for (var i = 0; i < SIZE; i++) {
+		var val = buf[i];
+		rms += val * val;
+	}
+	rms = Math.sqrt(rms / SIZE);
+	if (rms < 0.01)
+		// not enough signal
+		return -1;
+
+	var r1 = 0,
+		r2 = SIZE - 1,
+		thres = 0.2;
+	for (var i = 0; i < SIZE / 2; i++)
+		if (Math.abs(buf[i]) < thres) {
+			r1 = i;
+			break;
+		}
+	for (var i = 1; i < SIZE / 2; i++)
+		if (Math.abs(buf[SIZE - i]) < thres) {
+			r2 = SIZE - i;
+			break;
+		}
+
+	buf = buf.slice(r1, r2);
+	SIZE = buf.length;
+
+	var c = new Array(SIZE).fill(0);
+	for (var i = 0; i < SIZE; i++) for (var j = 0; j < SIZE - i; j++) c[i] = c[i] + buf[j] * buf[j + i];
+
+	var d = 0;
+	while (c[d] > c[d + 1]) d++;
+	var maxval = -1,
+		maxpos = -1;
+	for (var i = d; i < SIZE; i++) {
+		if (c[i] > maxval) {
+			maxval = c[i];
+			maxpos = i;
+		}
+	}
+	var T0 = maxpos;
+
+	var x1 = c[T0 - 1],
+		x2 = c[T0],
+		x3 = c[T0 + 1];
+	let a = (x1 + x3 - 2 * x2) / 2;
+	let b = (x3 - x1) / 2;
+	if (a) T0 = T0 - b / (2 * a);
+
+	return sampleRate / T0;
+}
+export const audioRecorder = {
+	/** 录音实例 */
+	audioContext: null as unknown as AudioContext,
+	mediaRecorder: null as any,
+	recordeState: false,
+	progress: (frequency: number) => {},
+	init() {
+		// 创建音频上下文
+		this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
+	},
+	/** 切换录音状态 */
+	toggleRecord(open: boolean) {
+		return new Promise((resolve, reject) => {
+			if (open) {
+				// 通过用户媒体设备获取音频流
+				navigator.mediaDevices
+					.getUserMedia({ audio: true })
+					.then((stream) => {
+						audioRecorder.mediaRecorder = stream.getAudioTracks()[0];
+						audioRecorder.recordeState = true;
+						// 创建音频源节点
+						const source = this.audioContext.createMediaStreamSource(stream);
+
+						// 创建分析器节点
+						const analyser = this.audioContext.createAnalyser();
+						analyser.fftSize = 2048; // 设置FFT大小,可以根据需要进行调整
+
+						// 连接源节点和分析器节点
+						source.connect(analyser);
+
+						var buf = new Float32Array(analyser.fftSize);
+						const sampleRate = this.audioContext.sampleRate;
+
+						function getData() {
+							if (!audioRecorder.recordeState) return;
+							analyser.getFloatTimeDomainData(buf);
+							const ac = autoCorrelate(buf, sampleRate);
+							audioRecorder.progress(ac);
+							setTimeout(() => {
+								getData();
+							}, 10);
+						}
+						getData();
+						resolve(true);
+					})
+					.catch((error) => {
+						resolve(false);
+						console.error("Error accessing media devices:", error);
+					});
+			} else {
+				audioRecorder.mediaRecorder.stop();
+			}
+		});
+	},
+
+	/** 检测是否支持录音 */
+	checkSupport(): Promise<boolean> {
+		return new Promise((resolve, reject) => {
+			if (navigator.mediaDevices.getUserMedia) {
+				const constraints = { audio: true };
+				navigator.mediaDevices.getUserMedia(constraints).then(
+					(stream) => {
+						resolve(true);
+						console.log("授权成功!");
+					},
+					() => {
+						resolve(false);
+						console.error("授权失败!");
+					}
+				);
+			} else {
+				resolve(false);
+				console.error("浏览器不支持 getUserMedia");
+			}
+		});
+	},
+};

+ 35 - 18
src/view/follow-practice/index.tsx

@@ -3,13 +3,15 @@ import state, { gotoNext, resetPlaybackToStart } from "/src/state";
 import { IPostMessage } from "/src/utils/native-message";
 import { api_cloudFollowTime, api_cloudToggleFollow } from "/src/helpers/communication";
 import { storeData } from "/src/store";
-import { Snackbar } from "@varlet/ui";
+import { audioRecorder } from "./audioRecorder";
 
 export const followData = reactive({
 	list: [] as any, // 频率列表
 	index: 0,
 	start: false,
 	rendered: false,
+	/** 麦克风权限 */
+	earphone: false,
 });
 
 /** 点击跟练模式 */
@@ -26,7 +28,7 @@ const noteFrequency = ref(0);
 const audioFrequency = ref(0);
 const followTime = ref(0);
 // 切换录音
-const openToggleRecord = (open: boolean = true) => {
+const openToggleRecord = async (open: boolean = true) => {
 	api_cloudToggleFollow(open ? "start" : "end");
 	// 记录跟练时长
 	if (open) {
@@ -37,6 +39,15 @@ const openToggleRecord = (open: boolean = true) => {
 			followTime.value = 0;
 		}
 	}
+
+	if (!storeData.isApp) {
+		const openState = await audioRecorder?.toggleRecord(open);
+		// 开启录音失败
+		if (!openState && followData.start) {
+			followData.earphone = true;
+			followData.start = false;
+		}
+	}
 };
 
 // 清除音符状态
@@ -55,20 +66,13 @@ const onClear = () => {
 
 /** 开始跟练 */
 export const handleFollowStart = () => {
-	// if (!storeData.isApp) {
-	// 	Snackbar({
-	// 		content: "请在APP端使用",
-	// 		type: "warning",
-	// 	});
-	// 	return;
-	// }
 	onClear();
 	followData.start = true;
 	followData.index = 0;
 	followData.list = [];
 	resetPlaybackToStart();
 	openToggleRecord(true);
-	getNoteIndex()
+	getNoteIndex();
 };
 /** 结束跟练 */
 export const handleFollowEnd = () => {
@@ -106,12 +110,12 @@ let checking = false;
 const onFollowTime = (evt?: IPostMessage) => {
 	const frequency: number = evt?.content?.frequency;
 	// 没有开始, 不处理
-	if (!followData.start) return
+	if (!followData.start) return;
 	// console.log('频率', frequency)
 	if (frequency > 0) {
-	  audioFrequency.value = frequency
-	  // data.list.push(frequency)
-	  checked()
+		audioFrequency.value = frequency;
+		// data.list.push(frequency)
+		checked();
 	}
 };
 
@@ -140,9 +144,9 @@ const checked = () => {
 				return;
 			}
 		}
-		console.log("频率对了", item.min, audioFrequency.value, item.max, item.duration);
+		// console.log("频率对了", item.min, audioFrequency.value, item.max, item.duration);
 	}
-	console.log("频率不对", item.min, audioFrequency.value, item.max, item.baseFrequency);
+	// console.log("频率不对", item.min, audioFrequency.value, item.max, item.baseFrequency);
 	setColor(item, audioFrequency.value > item.baseFrequency ? "follow-up" : "follow-down");
 	checking = false;
 };
@@ -166,8 +170,21 @@ const setColor = (item: any, state: "follow-up" | "follow-down" | "", isRight =
 export default defineComponent({
 	name: "follow",
 	setup() {
-		onMounted(() => {
-			api_cloudFollowTime(onFollowTime);
+		onMounted(async () => {
+			/** 如果是PC端 */
+			if (!storeData.isApp) {
+				const canRecorder = await audioRecorder.checkSupport();
+				if (canRecorder) {
+					audioRecorder.init();
+					audioRecorder.progress = (frequency: number) => {
+						onFollowTime({ api: "", content: { frequency } });
+					};
+				} else {
+					followData.earphone = true;
+				}
+			} else {
+				api_cloudFollowTime(onFollowTime);
+			}
 			console.log("进入跟练模式");
 		});
 		onUnmounted(() => {

+ 1 - 1
vite.config.ts

@@ -12,7 +12,7 @@ export default defineConfig({
 	base: "./",
 	resolve: {},
 	plugins: [
-		// mkcert(), // 本地https
+		mkcert(), // 本地https
 		legacy({
 			targets: 'last 2 versions and not dead, > 0.3%, Firefox ESR'
 		}),

Some files were not shown because too many files changed in this diff