Portal-Vue CSS editor

Use Portal-Vue to render a style tag in <head>

by Simon Herteby

HTML

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/portal-vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/sortable/1.4.2/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/15.0.0/vuedraggable.js"></script>
<div id="vue">
  <draggable v-model="styles">
    <div v-for="style, i in styles" class="style">
      <div>
        <input v-model="style.selector">
        <button @click="styles.splice(i,1)" tabindex="-1" class="delete">Remove style</button>
      </div>
      <div v-for="attribute, i in style.attributes">
        <input v-model="attribute.key">
        <input v-model="attribute.value" @keydown.enter="style.attributes.push({key:'',value:''})">
        <button @click="style.attributes.splice(i,1)" tabindex="-1" class="delete">-</button>
      </div>
      <button @click="style.attributes.push({key:'',value:''})" tabindex="-1">Add attribute</button>
    </div>
  </draggable>
  <button @click="styles.push({selector:'',attributes:[{key:'',value:''}]})" tabindex="-1">Add style</button>
  <button @click="reset" class="delete">Reset all</button>
  <portal target-el="#style">
    <template v-for="style in styles">
      {{style.selector}}{
      <template v-for="attribute in style.attributes">
        {{attribute.key}} : {{attribute.value}} ;
      </template>
      }
    </template>
  </portal>
</div>

JavaScript

let styleTag = document.createElement('style')
styleTag.id = 'style'
document.head.appendChild(styleTag)
let defaults = [{"selector":"html","attributes":[{"key":"height","value":"100%"}]},{"selector":"body","attributes":[{"key":"background","value":"linear-gradient(orange,magenta) fixed"}]},{"selector":".style","attributes":[{"key":"background","value":"white"},{"key":"padding","value":"10px"},{"key":"margin-bottom","value":"10px"},{"key":"box-shadow","value":"0px 1px 4px rgba(0,0,0,0.5)"}]},{"selector":".style div","attributes":[{"key":"display","value":"flex"}]},{"selector":".style div:first-child input","attributes":[{"key":"font-weight","value":"bold"}]},{"selector":"input, button","attributes":[{"key":"border-radius","value":"3px"},{"key":"margin","value":"2px"}]},{"selector":"input","attributes":[{"key":"border","value":"none"},{"key":"padding","value":"10px"},{"key":"box-shadow","value":"inset 0px 1px 2px rgba(0,0,0,0.3)"},{"key":"flex-grow","value":"1"}]},{"selector":"button","attributes":[{"key":"background","value":"linear-gradient(#0af,blue)"},{"key":"color","value":"white"},{"key":"border","value":"none"},{"key":"box-shadow","value":"0px 1px 2px rgba(0,0,0,0.3)"},{"key":"padding","value":"10px 15px"}]},{"selector":"button.delete","attributes":[{"key":"background","value":"linear-gradient(red,purple)"}]}]
new Vue({
  el: '#vue',
  data: {
    styles:JSON.parse(localStorage.getItem('styles')) || defaults
  },
  methods:{
  	reset(){
    	this.styles = defaults
      localStorage.removeItem('styles')
    }
  },
  watch: {
    styles: {
      handler(val) {
      	localStorage.setItem('styles', JSON.stringify(val))
        console.log(this.$data.styles)
        console.log(JSON.stringify(val))
      }, deep: true
    }
  }
})
console.log(localStorage)