add error handling, toasts, and loading spinner
This commit is contained in:
		
							parent
							
								
									9d9e6716d5
								
							
						
					
					
						commit
						ee1251467a
					
				
					 18 changed files with 298 additions and 13 deletions
				
			
		
							
								
								
									
										41
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										41
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1321,6 +1321,11 @@
 | 
			
		|||
        "stream-shift": "1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "eases-jsnext": {
 | 
			
		||||
      "version": "1.0.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eases-jsnext/-/eases-jsnext-1.0.10.tgz",
 | 
			
		||||
      "integrity": "sha512-1bO1+FIuqtOZpcyoIJuTnw8PU9X+RHHA248mZ1m+CPiiKFGCiNLWecITlhO4DXe7whZmBoJyfKwUoMW0KK5mNw=="
 | 
			
		||||
    },
 | 
			
		||||
    "ecc-jsbn": {
 | 
			
		||||
      "version": "0.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -6779,6 +6784,11 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/svelte/-/svelte-1.51.0.tgz",
 | 
			
		||||
      "integrity": "sha512-lqa9eAZ4ZQLMWsoyynAogUtib7HhHnrJJaS93uRgZU5cfXquBVR+FkKVK41LdlwffmOfOjbUin6pT8e/LZUwjA=="
 | 
			
		||||
    },
 | 
			
		||||
    "svelte-extras": {
 | 
			
		||||
      "version": "1.6.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svelte-extras/-/svelte-extras-1.6.0.tgz",
 | 
			
		||||
      "integrity": "sha512-0yzXHJdnaX3+KiLrDu9Hl6V7+idfKrUkYqhpbdnxCEJos2FSxtpos6cjAt+A2vVrdcNjFqtXYs6xS+rFWeg1yA=="
 | 
			
		||||
    },
 | 
			
		||||
    "svelte-loader": {
 | 
			
		||||
      "version": "2.3.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svelte-loader/-/svelte-loader-2.3.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -6788,6 +6798,37 @@
 | 
			
		|||
        "tmp": "0.0.31"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "svelte-transitions": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svelte-transitions/-/svelte-transitions-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha1-AaLpVPOnTXH8dtOn3Sn90VVRCBA=",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "svelte-transitions-fade": "1.0.0",
 | 
			
		||||
        "svelte-transitions-fly": "1.0.2",
 | 
			
		||||
        "svelte-transitions-slide": "1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "svelte-transitions-fade": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svelte-transitions-fade/-/svelte-transitions-fade-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-2+FSDfH1tTcL1hr+/Gfy0v/2MwM="
 | 
			
		||||
    },
 | 
			
		||||
    "svelte-transitions-fly": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svelte-transitions-fly/-/svelte-transitions-fly-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha1-CP02aUG0uSmpL5Y1uQJDpYbCuNc=",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "eases-jsnext": "1.0.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "svelte-transitions-slide": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svelte-transitions-slide/-/svelte-transitions-slide-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-FQ3Zy455+p4vJQ4ZjH1plgvyGZQ=",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "eases-jsnext": "1.0.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "svgo": {
 | 
			
		||||
      "version": "0.7.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,9 @@
 | 
			
		|||
    "serve-static": "^1.13.1",
 | 
			
		||||
    "style-loader": "^0.19.1",
 | 
			
		||||
    "svelte": "^1.50.0",
 | 
			
		||||
    "svelte-extras": "^1.6.0",
 | 
			
		||||
    "svelte-loader": "^2.3.3",
 | 
			
		||||
    "svelte-transitions": "^1.1.1",
 | 
			
		||||
    "uglifyjs-webpack-plugin": "^1.1.5",
 | 
			
		||||
    "url-search-params": "^0.10.0",
 | 
			
		||||
    "webpack": "^3.10.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										61
									
								
								routes/_components/LoadingMask.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								routes/_components/LoadingMask.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
<div class="loading-container">
 | 
			
		||||
  {{#if show}}
 | 
			
		||||
  <div transition:fade class="loading-mask">
 | 
			
		||||
    <svg>
 | 
			
		||||
      <use xlink:href="#fa-spinner" />
 | 
			
		||||
    </svg>
 | 
			
		||||
  </div>
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .loading-container {
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    z-index: 100;
 | 
			
		||||
  }
 | 
			
		||||
  .loading-mask {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    background: var(--mask-bg);
 | 
			
		||||
    opacity: 0.6;
 | 
			
		||||
    pointer-events: auto;
 | 
			
		||||
  }
 | 
			
		||||
  svg {
 | 
			
		||||
    width: 64px;
 | 
			
		||||
    height: 64px;
 | 
			
		||||
    fill: var(--mask-svg-fill);
 | 
			
		||||
    animation: spin 2s infinite linear;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @keyframes spin {
 | 
			
		||||
    0% {
 | 
			
		||||
      transform: rotate(0deg);
 | 
			
		||||
    }
 | 
			
		||||
    25% {
 | 
			
		||||
      transform: rotate(90deg);
 | 
			
		||||
    }
 | 
			
		||||
    50% {
 | 
			
		||||
      transform: rotate(180deg);
 | 
			
		||||
    }
 | 
			
		||||
    75% {
 | 
			
		||||
      transform: rotate(270deg);
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
      transform: rotate(360deg);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import { fade } from 'svelte-transitions'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    transitions: { fade }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										96
									
								
								routes/_components/Toast.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								routes/_components/Toast.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
<div class="toast-modal {{shown ? 'shown' : ''}}">
 | 
			
		||||
  <div class="toast-container">
 | 
			
		||||
    {{text}}
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
  .toast-modal {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    bottom: 40px;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transition: opacity 333ms linear;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .toast-container {
 | 
			
		||||
    max-width: 600px;
 | 
			
		||||
    max-height: 20vh;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    border: 2px solid var(--toast-border);
 | 
			
		||||
    background: var(--toast-bg);
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    margin: 0 40px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    color: var(--toast-text);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .toast-modal.shown {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 767px) {
 | 
			
		||||
    .toast-container {
 | 
			
		||||
      max-width: 80vw;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import { splice, push } from 'svelte-extras'
 | 
			
		||||
 | 
			
		||||
  const TIME_TO_SHOW_TOAST = 5000
 | 
			
		||||
  const DELAY_BETWEEN_TOASTS = 1000
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate () {
 | 
			
		||||
      this._queue = Promise.resolve()
 | 
			
		||||
      this.observe('messages', (messages) => {
 | 
			
		||||
        console.log('messages', messages)
 | 
			
		||||
        if (messages.length) {
 | 
			
		||||
          this.onNewToast(messages[0])
 | 
			
		||||
          this.splice('messages', 0, 1)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    ondestroy () {
 | 
			
		||||
    },
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      text: '',
 | 
			
		||||
      shown: false,
 | 
			
		||||
      messages: []
 | 
			
		||||
    }),
 | 
			
		||||
    methods: {
 | 
			
		||||
      push,
 | 
			
		||||
      splice,
 | 
			
		||||
      say(text) {
 | 
			
		||||
        this.push('messages', text)
 | 
			
		||||
      },
 | 
			
		||||
      onNewToast(text) {
 | 
			
		||||
        this._queue = this._queue.then(() => {
 | 
			
		||||
          this.set({
 | 
			
		||||
            'text': text,
 | 
			
		||||
            shown: true
 | 
			
		||||
          })
 | 
			
		||||
          return new Promise(resolve => {
 | 
			
		||||
            setTimeout(resolve, TIME_TO_SHOW_TOAST)
 | 
			
		||||
          })
 | 
			
		||||
        }).then(() => {
 | 
			
		||||
          this.set({
 | 
			
		||||
            shown: false
 | 
			
		||||
          })
 | 
			
		||||
          return new Promise(resolve => {
 | 
			
		||||
            setTimeout(resolve, DELAY_BETWEEN_TOASTS)
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										18
									
								
								routes/_utils/toast.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								routes/_utils/toast.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
import Toast from '../_components/Toast.html'
 | 
			
		||||
 | 
			
		||||
let toast
 | 
			
		||||
 | 
			
		||||
if (process.browser) {
 | 
			
		||||
  toast = new Toast({
 | 
			
		||||
    target: document.querySelector('#toast')
 | 
			
		||||
  })
 | 
			
		||||
  if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
    window.toast = toast // for debugging
 | 
			
		||||
  }
 | 
			
		||||
} else {
 | 
			
		||||
  toast = {
 | 
			
		||||
    say: () => {}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { toast }
 | 
			
		||||
| 
						 | 
				
			
			@ -4,8 +4,15 @@
 | 
			
		|||
<style>
 | 
			
		||||
  ul {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    width: 80%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border: 1px solid var(--settings-list-item-border);
 | 
			
		||||
    margin: 20px auto;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (min-width: 768px) {
 | 
			
		||||
    ul {
 | 
			
		||||
      max-width: 80%;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -13,16 +13,15 @@
 | 
			
		|||
 | 
			
		||||
<style>
 | 
			
		||||
  ul {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    margin: 5px 10px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
    margin: 5px 0;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  li::after {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,7 +68,7 @@
 | 
			
		|||
    justify-content: right;
 | 
			
		||||
  }
 | 
			
		||||
  .instance-actions button {
 | 
			
		||||
    margin: 0 20px;
 | 
			
		||||
    margin: 0 5px;
 | 
			
		||||
    flex-basis: 100%;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,8 @@
 | 
			
		|||
  <SettingsLayout page='settings/instances/add' label="Add an Instance">
 | 
			
		||||
    <h1>Add an Instance</h1>
 | 
			
		||||
 | 
			
		||||
    <LoadingMask show="{{loading}}"/>
 | 
			
		||||
 | 
			
		||||
    {{#if $isUserLoggedIn}}
 | 
			
		||||
    <p>Connect to an instance to log in.</p>
 | 
			
		||||
    {{else}}
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +17,7 @@
 | 
			
		|||
    <form class="add-new-instance" on:submit='onSubmit(event)'>
 | 
			
		||||
      <label for="instanceInput">Instance name:</label>
 | 
			
		||||
      <input type="text" id="instanceInput" bind:value='$instanceNameInSearch' placeholder=''>
 | 
			
		||||
      <button class="primary" type="submit" id="submitButton">Add instance</button>
 | 
			
		||||
      <button class="primary" type="submit" id="submitButton" disabled="{{!$instanceNameInSearch}}">Add instance</button>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
    {{#if !$isUserLoggedIn}}
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +55,9 @@
 | 
			
		|||
  import { store } from '../../_utils/store'
 | 
			
		||||
  import { goto } from 'sapper/runtime.js'
 | 
			
		||||
  import { switchToTheme } from '../../_utils/themeEngine'
 | 
			
		||||
  import { toast } from '../../_utils/toast'
 | 
			
		||||
  import LoadingMask from '../../_components/LoadingMask'
 | 
			
		||||
  import { fade } from 'svelte-transitions'
 | 
			
		||||
 | 
			
		||||
  const REDIRECT_URI = (typeof location !== 'undefined' ?
 | 
			
		||||
      location.origin : 'https://pinafore.social') + '/settings/instances/add'
 | 
			
		||||
| 
						 | 
				
			
			@ -70,17 +75,37 @@
 | 
			
		|||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      Layout,
 | 
			
		||||
      SettingsLayout
 | 
			
		||||
      SettingsLayout,
 | 
			
		||||
      LoadingMask
 | 
			
		||||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    transitions: {
 | 
			
		||||
      fade
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      onSubmit: async function(event) {
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
        this.set({loading: true})
 | 
			
		||||
        try {
 | 
			
		||||
          await this.redirectToOauth()
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
          if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
            console.error(err)
 | 
			
		||||
          }
 | 
			
		||||
          toast.say(`Error: ${err.message || err.name}. Is this a valid Mastodon instance?`)
 | 
			
		||||
        } finally {
 | 
			
		||||
          this.set({loading: false})
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      redirectToOauth: async function() {
 | 
			
		||||
        let instanceName = this.store.get('instanceNameInSearch')
 | 
			
		||||
        let loggedInInstances = this.store.get('loggedInInstances')
 | 
			
		||||
        instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '')
 | 
			
		||||
        // TODO: show toast error if you're already logged into this instance
 | 
			
		||||
        if (Object.keys(loggedInInstances).includes(instanceName)) {
 | 
			
		||||
          toast.say(`You've already logged in to ${instanceName}`)
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        let instanceData = await registerApplication(instanceName, REDIRECT_URI)
 | 
			
		||||
        // TODO: handle error
 | 
			
		||||
        this.store.set({
 | 
			
		||||
          currentRegisteredInstanceName: instanceName,
 | 
			
		||||
          currentRegisteredInstance: instanceData
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +119,16 @@
 | 
			
		|||
        document.location.href = oauthUrl
 | 
			
		||||
      },
 | 
			
		||||
      onReceivedOauthCode: async function(code) {
 | 
			
		||||
        try {
 | 
			
		||||
          this.set({loading: true})
 | 
			
		||||
          await this.registerNewInstance(code)
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
          toast.say(`Error: ${err.message || err.name}. Failed to connect to instance.`)
 | 
			
		||||
        } finally {
 | 
			
		||||
          this.set({loading: false})
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      registerNewInstance: async function (code) {
 | 
			
		||||
        let currentRegisteredInstanceName = this.store.get('currentRegisteredInstanceName')
 | 
			
		||||
        let currentRegisteredInstance = this.store.get('currentRegisteredInstance')
 | 
			
		||||
        let instanceData = await getAccessTokenFromAuthCode(
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +138,6 @@
 | 
			
		|||
          code,
 | 
			
		||||
          REDIRECT_URI
 | 
			
		||||
        )
 | 
			
		||||
        // TODO: handle error
 | 
			
		||||
        let loggedInInstances = this.store.get('loggedInInstances')
 | 
			
		||||
        let loggedInInstancesInOrder = this.store.get('loggedInInstancesInOrder')
 | 
			
		||||
        let instanceThemes = this.store.get('instanceThemes')
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +158,7 @@
 | 
			
		|||
        this.store.save()
 | 
			
		||||
        switchToTheme('default')
 | 
			
		||||
        goto('/')
 | 
			
		||||
      },
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +24,6 @@ main {
 | 
			
		|||
  background: var(--main-bg);
 | 
			
		||||
  border: 1px solid var(--main-border);
 | 
			
		||||
  border-radius: 1px;
 | 
			
		||||
  min-height: 50vh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1, h2, h3, h4, h5, h6 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,4 +47,11 @@
 | 
			
		|||
  --settings-list-item-border: $border-color;
 | 
			
		||||
  --settings-list-item-bg-active: darken($main-bg-color, 10%);
 | 
			
		||||
  --settings-list-item-bg-hover: darken($main-bg-color, 2%);
 | 
			
		||||
 | 
			
		||||
  --toast-bg: $toast-bg;
 | 
			
		||||
  --toast-border: $toast-border;
 | 
			
		||||
  --toast-text: $secondary-text-color;
 | 
			
		||||
 | 
			
		||||
  --mask-bg: $toast-bg;
 | 
			
		||||
  --mask-svg-fill: $secondary-text-color;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ $main-text-color: #333;
 | 
			
		|||
$border-color: #dadada;
 | 
			
		||||
$main-bg-color: white;
 | 
			
		||||
$secondary-text-color: white;
 | 
			
		||||
$toast-border: #fafafa;
 | 
			
		||||
$toast-bg: #333;
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ $main-text-color: #333;
 | 
			
		|||
$border-color: #dadada;
 | 
			
		||||
$main-bg-color: white;
 | 
			
		||||
$secondary-text-color: white;
 | 
			
		||||
$toast-border: #fafafa;
 | 
			
		||||
$toast-bg: #333;
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ $main-text-color: #333;
 | 
			
		|||
$border-color: #dadada;
 | 
			
		||||
$main-bg-color: white;
 | 
			
		||||
$secondary-text-color: white;
 | 
			
		||||
$toast-border: #fafafa;
 | 
			
		||||
$toast-bg: #333;
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ $main-text-color: #333;
 | 
			
		|||
$border-color: #dadada;
 | 
			
		||||
$main-bg-color: white;
 | 
			
		||||
$secondary-text-color: white;
 | 
			
		||||
$toast-border: #fafafa;
 | 
			
		||||
$toast-bg: #333;
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ $main-text-color: #333;
 | 
			
		|||
$border-color: #dadada;
 | 
			
		||||
$main-bg-color: white;
 | 
			
		||||
$secondary-text-color: white;
 | 
			
		||||
$toast-border: #fafafa;
 | 
			
		||||
$toast-bg: #333;
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ $main-text-color: #333;
 | 
			
		|||
$border-color: #dadada;
 | 
			
		||||
$main-bg-color: white;
 | 
			
		||||
$secondary-text-color: white;
 | 
			
		||||
$toast-border: #fafafa;
 | 
			
		||||
$toast-bg: #333;
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,11 +94,20 @@
 | 
			
		|||
			<path d="M576 736v192q0 40-28 68t-68 28H288q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm512 0v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm512 0v192q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68z"/>
 | 
			
		||||
		</symbol>
 | 
			
		||||
 | 
			
		||||
		<symbol id="fa-spinner" viewBox="0 0 1792 1792">
 | 
			
		||||
			<title>Spinner</title>
 | 
			
		||||
			<path d="M526 1394q0 53-37.5 90.5T398 1522q-52 0-90-38t-38-90q0-53 37.5-90.5T398 1266t90.5 37.5T526 1394zm498 206q0 53-37.5 90.5T896 1728t-90.5-37.5T768 1600t37.5-90.5T896 1472t90.5 37.5 37.5 90.5zM320 896q0 53-37.5 90.5T192 1024t-90.5-37.5T64 896t37.5-90.5T192 768t90.5 37.5T320 896zm1202 498q0 52-38 90t-90 38q-53 0-90.5-37.5T1266 1394t37.5-90.5 90.5-37.5 90.5 37.5 37.5 90.5zM558 398q0 66-47 113t-113 47-113-47-47-113 47-113 113-47 113 47 47 113zm1170 498q0 53-37.5 90.5T1600 1024t-90.5-37.5T1472 896t37.5-90.5T1600 768t90.5 37.5T1728 896zm-640-704q0 80-56 136t-136 56-136-56-56-136 56-136T896 0t136 56 56 136zm530 206q0 93-66 158.5T1394 622q-93 0-158.5-65.5T1170 398q0-92 65.5-158t158.5-66q92 0 158 66t66 158z"/>
 | 
			
		||||
		</symbol>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	</svg>
 | 
			
		||||
	<!-- The application will be rendered inside this element,
 | 
			
		||||
	     because `templates/main.js` references it -->
 | 
			
		||||
	<div id='sapper'>%sapper.html%</div>
 | 
			
		||||
 | 
			
		||||
	<!-- Toast.html gets rendered here -->
 | 
			
		||||
	<div id="toast"></div>
 | 
			
		||||
 | 
			
		||||
	<!-- Sapper creates a <script> tag containing `templates/main.js`
 | 
			
		||||
	     and anything else it needs to hydrate the app and
 | 
			
		||||
	     initialise the router -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue