Start working on violet panel

This commit is contained in:
Melon 2023-10-19 23:17:24 +01:00
parent ac116af353
commit f645402b76
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
21 changed files with 440 additions and 135 deletions

36
.editorconfig Normal file
View File

@ -0,0 +1,36 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Defaults
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
# CSS
[*.css]
indent_size = 2
indent_style = space
trim_trailing_whitespace = true
# HTML
[*.{htm,html}]
indent_size = 2
indent_style = space
trim_trailing_whitespace = true
# GNU make
[Makefile]
indent_style = tab
# Svelte
[*.svelte]
indent_size = 2
indent_style = space
# YAML
[*.{yaml,yml}]
indent_size = 2
indent_style = space

4
.env.production Normal file
View File

@ -0,0 +1,4 @@
VITE_SSO_ORIGIN=https://sso.1f349.net
VITE_API_VIOLET=https://api.1f349.net/v1/violet
VITE_API_ORCHID=https://api.1f349.net/v1/orchid

25
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Build/release
on: [push]
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v3
with:
node-version: 18
- run: yarn
- run: yarn build
- name: Archive
run: tar -czvf upload.tar.gz -C ./dist .
- name: Release
run: 'curl -X POST -H "Authorization: Bearer ${{ secrets.DEPLOY }}" -F "upload=@upload.tar.gz" "https://sites.1f349.net/u?site=admin.1f349.net&branch=${{ github.ref_name }}"'

13
.prettierrc Normal file
View File

@ -0,0 +1,13 @@
{
"printWidth": 150,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": false,
"bracketSameLine": false,
"arrowParens": "avoid",
"requirePragma": false,
"insertPragma": false
}

View File

@ -1,10 +1,16 @@
<!doctype html>
<html lang="en">
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
<title>1f349 Admin Dashboard</title>
<meta name="author" content="1f349.net" />
<meta property="og:title" content="1f349 Admin Dashboard" />
<meta property="og:url" content="https://admin.1f349.net" />
<meta property="og:type" content="object" />
<meta property="og:image" content="https://admin.1f349.net/1f349.svg" />
<meta property="og:site_name" content="1f349 Admin Dashboard" />
<link rel="icon" type="image/svg+xml" href="/1f349.svg" />
</head>
<body>
<div id="app"></div>

View File

@ -4,6 +4,8 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"prettier:check:ci": "./node_modules/.bin/prettier --check .",
"format": "./node_modules/.bin/prettier --write .",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
@ -12,6 +14,7 @@
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
"@tsconfig/svelte": "^5.0.0",
"sass": "^1.69.4",
"svelte": "^4.0.5",
"svelte-check": "^3.4.6",
"tslib": "^2.6.0",

7
public/1f349.svg Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<path fill="#5C913B" d="M2.472 6.572C1.528 8.698 1 11.038 1 13.5 1 23.165 9.059 31 19 31c7.746 0 14.33-4.767 16.868-11.44L2.472 6.572z"/>
<path fill="#FFE8B6" d="M4.332 7.295C3.479 9.197 3 11.293 3 13.5c0 8.591 7.164 15.556 16 15.556 6.904 0 12.77-4.26 15.013-10.218L4.332 7.295z"/>
<path fill="#DD2E44" d="M6.191 8.019C5.43 9.697 5 11.548 5 13.5c0 7.518 6.268 13.611 14 13.611 6.062 0 11.21-3.753 13.156-8.995L6.191 8.019z"/>
<path d="M9.916 14.277c-.307.46-.741.708-.971.555-.23-.153-.168-.649.139-1.109.307-.46.741-.708.971-.555.23.153.168.649-.139 1.109zm6 1c-.307.46-.741.708-.971.555-.23-.153-.168-.649.139-1.109.307-.46.741-.708.971-.555.23.153.168.649-.139 1.109zm5.082 4.678c.05.551-.132 1.016-.406 1.041-.275.025-.538-.4-.588-.951-.051-.551.132-1.016.406-1.04.275-.026.538.398.588.95zm-9-2c.05.551-.132 1.016-.406 1.041-.275.025-.538-.4-.588-.951-.05-.551.132-1.016.406-1.04.276-.026.538.398.588.95zm3.901 5.346c-.333.441-.78.663-1 .497-.221-.166-.129-.658.205-1.099.333-.441.781-.663 1-.497.221.166.13.657-.205 1.099zm8.036.454c.273.481.299.979.06 1.115-.241.137-.656-.143-.929-.624-.273-.48-.299-.979-.059-1.115.241-.138.655.141.928.624zm-7.017-5.028c.303.463.362.958.131 1.109-.231.152-.663-.1-.966-.562-.303-.462-.361-.958-.131-1.108.231-.154.663.097.966.561zm8.981 1.574c-.333.441-.78.663-1.001.497-.221-.166-.129-.658.205-1.099.333-.442.78-.663 1-.497.222.166.131.657-.204 1.099z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,47 +1,161 @@
<script lang="ts">
import svelteLogo from './assets/svelte.svg'
import viteLogo from '/vite.svg'
import Counter from './lib/Counter.svelte'
import type {SvelteComponent} from "svelte";
import GeneralView from "./views/GeneralView.svelte";
import VioletView from "./views/VioletView.svelte";
import OrchidView from "./views/OrchidView.svelte";
import {loginStore} from "./stores/login";
import {openLoginPopup} from "./utils/login-popup";
let sidebarOptions: Array<{name: string; view: typeof SvelteComponent<{}>}> = [
{name: "General", view: GeneralView},
{name: "Violet", view: VioletView},
{name: "Orchid", view: OrchidView},
];
let sidebarSelection: {name: string; view: typeof SvelteComponent<{}>} = sidebarOptions[0];
let tokenPerms: string[] = [];
$: tokenPerms = [];
</script>
<main>
<header>
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} class="logo" alt="Vite Logo" />
</a>
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
</a>
<h1>🍉 - 1f349 Admin Dashboard</h1>
</div>
<h1>Vite + Svelte</h1>
<div class="card">
<Counter />
<div class="flex-gap" />
<div class="nav-link">
<a href="https://status.1f349.net" target="_blank">Status</a>
</div>
{#if $loginStore == null}
<div class="login-view">
<button on:click={() => openLoginPopup()}>Login</button>
</div>
{:else}
<div class="user-view">
<img class="user-avatar" src={$loginStore.userinfo.picture} alt="{$loginStore.userinfo.name}'s profile picture" />
<div class="user-display-name">{$loginStore.userinfo.name}</div>
<button
on:click={() => {
$loginStore = null;
localStorage.removeItem("login-session");
}}>Logout</button
>
</div>
{/if}
</header>
<main>
<div id="sidebar">
{#each sidebarOptions as item (item.name)}
<button class="sidebar-item" on:click={() => (sidebarSelection = item)} class:selected={item == sidebarSelection}>{item.name}</button>
{/each}
</div>
<div id="option-view">
<h2>{sidebarSelection.name}</h2>
{#if $loginStore == null}
<div>Please login to continue</div>
{:else}
<svelte:component this={sidebarSelection.view} />
{/if}
</div>
<p>
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank" rel="noreferrer">SvelteKit</a>, the official Svelte app framework powered by Vite!
</p>
<p class="read-the-docs">
Click on the Vite and Svelte logos to learn more
</p>
</main>
<style>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
<style lang="scss">
header {
display: flex;
align-items: center;
justify-content: space-between;
height: 70px;
padding: 0 32px;
background-color: #2c2c2c;
box-shadow: 0 4px 8px #0003, 0 6px 20px #00000030;
gap: 16px;
z-index: 1;
position: relative;
h1 {
font-size: 32px;
margin: 0;
}
.nav-link {
font-size: 24px;
}
.flex-gap {
flex-grow: 1;
}
.user-view {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
}
button,
a {
background-color: transparent;
border: none;
box-shadow: none;
box-sizing: border-box;
color: tomato;
cursor: pointer;
font-size: 20px;
font-weight: 700;
line-height: 24px;
padding: 8px;
border-radius: 0.375rem;
}
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.svelte:hover {
filter: drop-shadow(0 0 2em #ff3e00aa);
}
.read-the-docs {
color: #888;
main {
display: flex;
flex-grow: 1;
align-items: stretch;
height: calc(100% - 70px);
#sidebar {
width: 200px;
button {
background-color: #2c2c2c;
border: none;
box-shadow: none;
box-sizing: border-box;
color: tomato;
cursor: pointer;
font-size: 20px;
font-weight: 700;
line-height: 24px;
width: 100%;
height: 70px;
&:hover {
background-color: #1c1c1c;
}
&.selected {
background-color: #1c1c1c;
}
}
}
#option-view {
box-sizing: border-box;
padding: 16px;
overflow-y: auto;
height: 100%;
flex-grow: 1;
h2 {
margin: 0;
margin-bottom: 16px;
}
}
}
</style>

View File

@ -1,80 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

46
src/app.scss Normal file
View File

@ -0,0 +1,46 @@
$theme-text: rgba(255, 255, 255, 0.87);
$theme-bg: #242424;
@media (prefers-color-scheme: light) {
$theme-text: #213547;
$theme-bg: #ffffff;
}
:root {
font-family: Ubuntu, Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: normal;
font-weight: 400;
color-scheme: light dark;
color: $theme-text;
background-color: $theme-bg;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
#app {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-content: stretch;
}
a {
font-weight: 500;
color: tomato;
text-decoration: inherit;
}
a:hover {
color: tomato;
}
body {
margin: 0;
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,10 +0,0 @@
<script lang="ts">
let count: number = 0
const increment = () => {
count += 1
}
</script>
<button on:click={increment}>
count is {count}
</button>

View File

@ -1,4 +1,4 @@
import './app.css'
import './app.scss'
import App from './App.svelte'
const app = new App({

28
src/stores/login.ts Normal file
View File

@ -0,0 +1,28 @@
import {writable} from "svelte/store";
export interface LoginStore {
userinfo: {
email: string;
name: string;
sub: string;
picture: string;
};
tokens: {
access: string;
refresh: string;
};
}
export const loginStore = writable<LoginStore | null>(
(() => {
try {
return JSON.parse(localStorage.getItem("login-session") || "");
} catch (_) {
return null;
}
})(),
);
loginStore.subscribe(x => {
localStorage.setItem("login-session", JSON.stringify(x));
});

53
src/utils/login-popup.ts Normal file
View File

@ -0,0 +1,53 @@
import {loginStore} from "../stores/login";
let currentLoginPopup: {close: () => void} | null = null;
const ssoOrigin = import.meta.env.VITE_SSO_ORIGIN;
window.addEventListener("message", function (event) {
if (event.origin !== ssoOrigin) return;
if (isObject(event.data)) {
loginStore.set(event.data);
if (currentLoginPopup) currentLoginPopup.close();
return;
}
alert("Failed to log user in: the login data was probably corrupted");
});
function isObject(obj: Object) {
return obj != null && obj.constructor.name === "Object";
}
function popupCenterScreen(url: string, title: string, w: number, h: number, focus: boolean) {
const top = (screen.availHeight - h) / 4,
left = (screen.availWidth - w) / 2;
const popup = openWindow(url, title, `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`);
if (focus === true && window.focus !== undefined && popup !== null) popup.focus();
return popup;
}
function openWindow(url: string | URL, winnm: string, options: string): Window | null {
var wTop = firstAvailableValue([window.screen.availTop, window.screenY, window.screenTop, 0]);
var wLeft = firstAvailableValue([window.screen.availLeft, window.screenX, window.screenLeft, 0]);
let w: Window | null;
var top = 0,
left = 0;
var result;
if ((result = /top=(\d+)/g.exec(options))) top = parseInt(result[1]);
if ((result = /left=(\d+)/g.exec(options))) left = parseInt(result[1]);
if (options) {
options = options.replace("top=" + top, "top=" + (top + wTop));
options = options.replace("left=" + left, "left=" + (left + wLeft));
w = window.open(url, winnm, options);
} else w = window.open(url, winnm);
return w;
}
function firstAvailableValue(arr: any) {
for (var i = 0; i < arr.length; i++) if (typeof arr[i] != "undefined") return arr[i];
}
export function openLoginPopup() {
if (currentLoginPopup) currentLoginPopup.close();
currentLoginPopup = popupCenterScreen(ssoOrigin + "/popup?origin=" + encodeURIComponent(location.origin), "Login with 1f349 SSO", 500, 500, false);
}

View File

@ -0,0 +1 @@
<div>No options available here</div>

View File

@ -0,0 +1 @@
<div>No options available here: {import.meta.env.VITE_API_ORCHID}</div>

View File

@ -0,0 +1,36 @@
<script lang="ts">
import {loginStore, type LoginStore} from "../stores/login";
const apiViolet = import.meta.env.VITE_API_VIOLET;
let promiseForRoutes = new Promise(async (res, rej) => {
fetch(apiViolet + "/route", {headers: {Authorization: "Bearer " + ($loginStore as LoginStore).tokens.access}})
.then(x => {
if(x.status!==200) throw new Error("Unexpected status code: " + x.status);
return x.json();
})
.then(x => res(x))
.catch(x => rej(x));
});
</script>
{#await promiseForRoutes}
<div>Loading...</div>
{:then allRoutes}
<table>
<tr>
<th>Source</th>
<th>Destination</th>
<th>Flags</th>
<th>Active</th>
</tr>
<tr>
<td />
<td />
<td />
<td />
</tr>
</table>
{:catch err}
<div>{err}</div>
{/await}

10
src/vite-env.d.ts vendored
View File

@ -1,2 +1,12 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
interface ImportMetaEnv {
VITE_SSO_ORIGIN: string;
VITE_API_VIOLET: string;
VITE_API_ORCHID: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -270,7 +270,7 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
chokidar@^3.4.1:
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@ -430,6 +430,11 @@ graceful-fs@^4.1.3:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
immutable@^4.0.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@ -675,6 +680,15 @@ sander@^0.5.0:
mkdirp "^0.5.1"
rimraf "^2.5.2"
sass@^1.69.4:
version "1.69.4"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.4.tgz#10c735f55e3ea0b7742c6efa940bce30e07fbca2"
integrity sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sorcery@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.11.0.tgz#310c80ee993433854bb55bb9aa4003acd147fca8"
@ -685,7 +699,7 @@ sorcery@^0.11.0:
minimist "^1.2.0"
sander "^0.5.0"
source-map-js@^1.0.1, source-map-js@^1.0.2:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==