Compare commits
No commits in common. "32e5c79e888362fc517e8f145f9a9f2b6b84dc54" and "df9e3a9f61a4b76d693c324609265f421b268fde" have entirely different histories.
32e5c79e88
...
df9e3a9f61
|
@ -34,7 +34,7 @@ class ProgramQuerySet(PageQuerySet):
|
|||
"""
|
||||
if user.is_superuser:
|
||||
return self
|
||||
groups = user.groups.all()
|
||||
groups = self.request.user.groups.all()
|
||||
return self.filter(editors_group__in=groups)
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ Usefull context:
|
|||
<meta name="description" content="{{ site.description }}" />
|
||||
<meta name="keywords" content="{{ site.tags }}" />
|
||||
<meta name="generator" content="Aircox" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="{% thumbnail site.favicon 32x32 crop %}" />
|
||||
|
||||
{% block assets %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="dropdown-trigger">
|
||||
<button class="button square" aria-haspopup="true" aria-controls="dropdown-menu" type="button">
|
||||
<span class="icon">
|
||||
<i class="fa-regular fa-user" aria-hidden="true" style="opacity: 0.6"></i>
|
||||
<i class="fa fa-user" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -40,16 +40,11 @@
|
|||
{% translate "Statistics" %}
|
||||
</a>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<hr class="dropdown-divider" />
|
||||
<form id="logout" action="{% url 'logout' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<a class="dropdown-item" href="#" type="submit" onclick="document.getElementById('logout').submit();">
|
||||
{% translate "Disconnect" %}
|
||||
</a>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a class="dropdown-item" href="{% url "logout" %}" data-force-reload="1">
|
||||
{% translate "Disconnect" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -136,11 +136,6 @@ export default class PageLoad {
|
|||
history.pushState(state, '', url)
|
||||
}
|
||||
|
||||
dispatchPageLoaded(url) {
|
||||
var evt = new CustomEvent("pageLoaded", {detail: url})
|
||||
document.dispatchEvent(evt)
|
||||
}
|
||||
|
||||
// --- events
|
||||
pageChanged(event) {
|
||||
let submit = event.type == 'submit';
|
||||
|
@ -166,7 +161,7 @@ export default class PageLoad {
|
|||
else
|
||||
options = {...options, method: target.method, body: formData}
|
||||
}
|
||||
this.load(url, options).then(() => this.dispatchPageLoaded(url)).then(() => this.historySave(url))
|
||||
this.load(url, options).then(() => this.historySave(url))
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# aircox
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
4324
radiocampus/assets/package-lock.json
generated
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"name": "aircox",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"core-js": "^3.8.3",
|
||||
"lodash": "^4.17.21",
|
||||
"v-calendar": "^3.1.2",
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vue": "^3.4.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tiptap/extension-link": "^2.3.0",
|
||||
"@tiptap/extension-underline": "^2.3.0",
|
||||
"@tiptap/pm": "^2.3.0",
|
||||
"@tiptap/starter-kit": "^2.3.0",
|
||||
"@tiptap/vue-3": "^2.3.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"bulma": "^0.9.4",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"sass": "^1.49.9",
|
||||
"vite": "^5.2.8"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
"es2022": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import './styles/admin.scss'
|
||||
import './index.js'
|
||||
|
||||
import App from './app';
|
||||
import components from './components/admin.js'
|
||||
|
||||
const AdminApp = {
|
||||
...App,
|
||||
components: {...App.components, ...components},
|
||||
|
||||
data() {
|
||||
return {
|
||||
...super.data,
|
||||
modalItem: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...App.methods,
|
||||
|
||||
fileSelected(select, input, preview) {
|
||||
const item = this.$refs[select].item
|
||||
if(item) {
|
||||
this.$refs[input].value = item.id
|
||||
if(preview)
|
||||
preview.src = item.file
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
export default AdminApp;
|
||||
|
||||
|
||||
window.App = AdminApp
|
|
@ -1,45 +0,0 @@
|
|||
import {Calendar, DatePicker} from 'v-calendar';
|
||||
import components from './components'
|
||||
|
||||
const App = {
|
||||
el: '#app',
|
||||
delimiters: ['[[', ']]'],
|
||||
components: {
|
||||
...components,
|
||||
...{
|
||||
VCalendar: Calendar,
|
||||
VDatepicker: DatePicker
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
player() { return window.aircox.player; },
|
||||
},
|
||||
|
||||
methods: {
|
||||
//! Delete elements from DOM using provided selector.
|
||||
deleteElements(sel) {
|
||||
for(var el of document.querySelectorAll(sel))
|
||||
el.parentNode.removeChild(el)
|
||||
},
|
||||
|
||||
//! File has been selected
|
||||
//! TODO: replace using regular ref and bindings.
|
||||
fileSelected(select, input, preview) {
|
||||
const item = this.$refs[select].item
|
||||
if(item) {
|
||||
this.$refs[input].value = item.id
|
||||
if(preview)
|
||||
preview.src = item.file
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const PlayerApp = {
|
||||
el: '#player',
|
||||
delimiters: ['[[', ']]'],
|
||||
components: {...components},
|
||||
}
|
||||
|
||||
export default App
|
|
@ -1,27 +0,0 @@
|
|||
// Enable styling body background while using vue hotreload
|
||||
// Tags with side effect (<script> and <style>) are ignored in client component templates.
|
||||
|
||||
const backgrounds = new Map();
|
||||
backgrounds.set('default', "linear-gradient(#738ef2, white)");
|
||||
backgrounds.set('/', "url(/static/radiocampus/backgrounds/photo-04-20.jpg) no-repeat center center fixed");
|
||||
|
||||
|
||||
export default class BackgroundLoad {
|
||||
constructor () {
|
||||
let url = new URL(document.location)
|
||||
this.path = url.pathname
|
||||
this.update()
|
||||
document.addEventListener("pageLoaded", this.handlePageLoad.bind(this), false)
|
||||
}
|
||||
|
||||
handlePageLoad (e) {
|
||||
this.path = e.detail
|
||||
this.update()
|
||||
}
|
||||
|
||||
update () {
|
||||
let background = backgrounds.get(this.path) || backgrounds.get("default")
|
||||
document.body.style.background = background;
|
||||
document.body.style.backgroundSize = "cover";
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<template>
|
||||
<component :is="tag" @click.capture.stop="call" type="button" :class="[buttonClass, this.promise && 'blink' || '']">
|
||||
<span v-if="promise && runIcon">
|
||||
<i :class="runIcon"></i>
|
||||
</span>
|
||||
<span v-else-if="icon" class="icon is-small">
|
||||
<i :class="icon"></i>
|
||||
</span>
|
||||
<span v-if="$slots.default"><slot name="default"/></span>
|
||||
</component>
|
||||
</template>
|
||||
<script>
|
||||
import Model from '../model'
|
||||
|
||||
/**
|
||||
* Button that can be used to call API requests on provided url
|
||||
*/
|
||||
export default {
|
||||
emit: ['start', 'done'],
|
||||
|
||||
props: {
|
||||
//! Component tag, by default, `button`
|
||||
tag: { type: String, default: 'a'},
|
||||
//! Button icon
|
||||
icon: String,
|
||||
//! Data or model instance to send
|
||||
data: Object,
|
||||
//! Action method, by default, `POST`
|
||||
method: { type: String, default: 'POST'},
|
||||
//! If provided open confirmation box before proceeding
|
||||
confirm: { type: String, default: ''},
|
||||
//! Action url
|
||||
url: String,
|
||||
//! Extra request options
|
||||
fetchOptions: {type: Object, default: () => {return {}}},
|
||||
//! Component class while action is running
|
||||
runClass: String,
|
||||
//! Icon class while action is running
|
||||
runIcon: String,
|
||||
},
|
||||
|
||||
computed: {
|
||||
//! Input data as model instance
|
||||
item() {
|
||||
return this.data instanceof Model ? this.data
|
||||
: new Model(this.data)
|
||||
},
|
||||
|
||||
//! Computed button class
|
||||
buttonClass() {
|
||||
return this.promise ? this.runClass : ''
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
promise: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
call() {
|
||||
if(this.promise || !this.url)
|
||||
return
|
||||
if(this.confirm && !confirm(this.confirm))
|
||||
return
|
||||
|
||||
const options = Model.getOptions({
|
||||
...this.fetchOptions,
|
||||
method: this.method,
|
||||
body: JSON.stringify(this.item.data),
|
||||
})
|
||||
this.promise = fetch(this.url, options).then(data => data.text()).then(data => {
|
||||
data = data && JSON.parse(data) || null
|
||||
this.promise = null;
|
||||
this.$emit('done', data)
|
||||
return data
|
||||
}, data => { this.promise = null; return data })
|
||||
return this.promise
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,293 +0,0 @@
|
|||
<template>
|
||||
<div class="control">
|
||||
<input type="hidden" :name="name" :value="selectedValue"
|
||||
@change="$emit('change', $event)"/>
|
||||
<input type="text" ref="input" class="input is-fullwidth" :class="inputClass"
|
||||
v-show="!button || !selected"
|
||||
v-model="inputValue"
|
||||
:placeholder="placeholder"
|
||||
@keydown.capture="onKeyDown"
|
||||
@keyup="onKeyUp($event); $emit('keyup', $event)"
|
||||
@keydown="$emit('keydown', $event)"
|
||||
@keypress="$emit('keypress', $event)"
|
||||
@focus="onInputFocus" @blur="onBlur" />
|
||||
<a v-if="selected && button"
|
||||
class="button is-normal is-fullwidth has-text-left is-inline-block overflow-hidden"
|
||||
@click="select(-1, false, true)">
|
||||
<span class="icon is-small ml-1">
|
||||
<i class="fa fa-pen"></i>
|
||||
</span>
|
||||
<span class="is-inline-block" v-if="selected">
|
||||
<slot name="button" :index="selectedIndex" :item="selected"
|
||||
:value-field="valueField" :labelField="labelField">
|
||||
{{ selectedLabel }}
|
||||
</slot>
|
||||
</span>
|
||||
</a>
|
||||
<div :class="dropdownClass">
|
||||
<div class="dropdown-menu is-fullwidth">
|
||||
<div class="dropdown-content" style="overflow: hidden">
|
||||
<span v-for="(item, index) in items" :key="item.id"
|
||||
:data-autocomplete-index="index"
|
||||
@click="select(index, false, false)"
|
||||
:class="['dropdown-item', (index == this.cursor) ? 'is-active':'']"
|
||||
tabindex="-1">
|
||||
<slot name="item" :index="index" :item="item" :value-field="valueField"
|
||||
:labelField="labelField">
|
||||
{{ getValue(item, labelField) || item }}
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import debounce from 'lodash/debounce'
|
||||
import Model from '../model'
|
||||
|
||||
|
||||
export default {
|
||||
emit: ['change', 'keypress', 'keydown', 'keyup', 'select', 'unselect',
|
||||
'update:modelValue'],
|
||||
|
||||
props: {
|
||||
//! Search URL (where `${query}` is replaced by search term)
|
||||
url: String,
|
||||
//! Extra GET url parameters
|
||||
urlParams: Object,
|
||||
//! Items' model
|
||||
model: Function,
|
||||
//! Input tag class
|
||||
inputClass: Array,
|
||||
//! input text placeholder
|
||||
placeholder: Object,
|
||||
//! input form field name
|
||||
name: String,
|
||||
//! Field on items to use as label
|
||||
labelField: String,
|
||||
//! Field on selected item to get selectedValue from, if any
|
||||
valueField: {type: String, default: null},
|
||||
count: {type: Number, count: 10},
|
||||
//! If true, show button when value has been selected
|
||||
button: Boolean,
|
||||
//! If true, value must come from a selection
|
||||
mustExist: {type: Boolean, default: false},
|
||||
//! Minimum input size before fetching
|
||||
minFetchLength: {type: Number, default: 3},
|
||||
modelValue: {default: ''},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
inputValue: this.modelValue || '',
|
||||
query: '',
|
||||
items: [],
|
||||
selectedIndex: -1,
|
||||
cursor: -1,
|
||||
promise: null,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
this.inputValue = value
|
||||
},
|
||||
|
||||
inputValue(value, old) {
|
||||
if(value != old && value != this.modelValue) {
|
||||
this.$emit('update:modelValue', value)
|
||||
this.$emit('change', {target: this.$refs.input})
|
||||
}
|
||||
if(this.selectedLabel != value)
|
||||
this.selectedIndex = -1
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
fullUrl() {
|
||||
if(!this.urlParams)
|
||||
return this.url
|
||||
|
||||
const url = new URL(this.url, window.location.origin)
|
||||
const params = new URLSearchParams(url.searchParams)
|
||||
|
||||
for(var key in this.urlParams)
|
||||
params.set(key, this.urlParams[key])
|
||||
const join = this.url.indexOf("?") >= 0 ? "&" : "?"
|
||||
url.search = params.toString()
|
||||
return url.href
|
||||
},
|
||||
|
||||
isFetching() { return !!this.promise },
|
||||
|
||||
selected() {
|
||||
let index = this.selectedIndex
|
||||
if(index<0)
|
||||
return null
|
||||
index = Math.min(index, this.items.length-1)
|
||||
return this.items[index]
|
||||
},
|
||||
|
||||
selectedValue() {
|
||||
let value = this.itemValue(this.selected)
|
||||
if(!value && !this.mustExist)
|
||||
value = this.inputValue
|
||||
return value
|
||||
},
|
||||
|
||||
selectedLabel() {
|
||||
return this.itemLabel(this.selected)
|
||||
},
|
||||
|
||||
dropdownClass() {
|
||||
var active = this.cursor > -1 && this.items.length;
|
||||
if(active && this.items.length == 1 &&
|
||||
this.itemValue(this.items[0]) == this.inputValue)
|
||||
active = false
|
||||
return ['dropdown is-fullwidth', active ? 'is-active':'']
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
reset() {
|
||||
this.inputValue = ""
|
||||
this.selectedIndex = -1
|
||||
this.items = []
|
||||
},
|
||||
|
||||
// TODO: move to utils/data
|
||||
getValue(data, path=null) {
|
||||
if(!data)
|
||||
return null
|
||||
if(!path)
|
||||
return data
|
||||
|
||||
const paths = path.split('.')
|
||||
for(const key of paths) {
|
||||
if(key in data)
|
||||
data = data[key]
|
||||
else return null;
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
itemValue(item) {
|
||||
return this.valueField ? this.getValue(item, this.valueField) : item;
|
||||
},
|
||||
|
||||
itemLabel(item) {
|
||||
return this.labelField ? this.getValue(item, this.labelField) : item;
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.cursor = -1;
|
||||
this.selectedIndex = -1;
|
||||
},
|
||||
|
||||
move(index=-1, relative=false) {
|
||||
if(relative)
|
||||
index += this.cursor
|
||||
this.cursor = Math.max(-1, Math.min(index, this.items.length-1))
|
||||
},
|
||||
|
||||
select(index=-1, relative=false, active=null) {
|
||||
if(relative)
|
||||
index += this.selectedIndex
|
||||
else if(index == this.selectedIndex)
|
||||
return
|
||||
|
||||
this.selectedIndex = Math.max(-1, Math.min(index, this.items.length-1))
|
||||
if(index >= 0) {
|
||||
this.inputValue = this.selectedLabel
|
||||
this.$refs.input.focus()
|
||||
}
|
||||
if(this.selectedIndex < 0)
|
||||
this.$emit('unselect')
|
||||
else
|
||||
this.$emit('select', index, this.selected, this.selectedValue)
|
||||
|
||||
if(active!==null)
|
||||
active && this.move(0) || this.move(-1)
|
||||
},
|
||||
|
||||
onInputFocus() {
|
||||
this.cursor < 0 && this.move(0)
|
||||
},
|
||||
|
||||
onBlur(event) {
|
||||
if(!this.items.length)
|
||||
return
|
||||
|
||||
var index = event.relatedTarget && Math.floor(event.relatedTarget.dataset.autocompleteIndex);
|
||||
if(index !== undefined && index !== null)
|
||||
this.select(index, false, false)
|
||||
this.cursor = -1;
|
||||
},
|
||||
|
||||
onKeyDown(event) {
|
||||
if(event.ctrlKey || event.altKey || event.metaKey)
|
||||
return
|
||||
switch(event.keyCode) {
|
||||
case 13: this.select(this.cursor, false, false)
|
||||
break
|
||||
case 27: this.hide(); this.select()
|
||||
break
|
||||
case 38: this.move(-1, true)
|
||||
break
|
||||
case 40: this.move(1, true)
|
||||
break
|
||||
default: return
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
},
|
||||
|
||||
onKeyUp(event) {
|
||||
if(event.ctrlKey || event.altKey || event.metaKey)
|
||||
return
|
||||
|
||||
const value = event.target.value
|
||||
if(value === this.query)
|
||||
return
|
||||
|
||||
this.inputValue = value;
|
||||
if(!value)
|
||||
return this.selected && this.select(-1)
|
||||
if(!this.minFetchLength || value.length >= this.minFetchLength)
|
||||
this.fetch(value)
|
||||
},
|
||||
|
||||
fetch(query) {
|
||||
if(!query || this.promise)
|
||||
return
|
||||
|
||||
this.query = query
|
||||
var url = this.fullUrl.replace('${query}', query).replace('%24%7Bquery%7D', query)
|
||||
var promise = this.model ? this.model.fetch(url, {many:true})
|
||||
: fetch(url, Model.getOptions()).then(d => d.json())
|
||||
|
||||
promise = promise.then(items => {
|
||||
if(items.results)
|
||||
items = items.results
|
||||
this.items = items.filter((i) => i) || []
|
||||
this.promise = null;
|
||||
this.move(0)
|
||||
return items
|
||||
}, data => {this.promise = null; Promise.reject(data)})
|
||||
this.promise = promise
|
||||
return promise
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const form = this.$el.closest('form')
|
||||
form && form.addEventListener('reset', () => {
|
||||
this.inputValue = this.value;
|
||||
this.select(-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -1,242 +0,0 @@
|
|||
<template>
|
||||
<section class="a-carousel">
|
||||
<nav ref="viewport" class="a-carousel-viewport">
|
||||
<section ref="container" :class="['a-carousel-container', containerClass]">
|
||||
<slot name="default"></slot>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<nav class="a-carousel-bullets-container">
|
||||
<span class="left">
|
||||
<span class="icon bullet" @click="prev()" v-if="showPrev">
|
||||
<i :class="leftButtonIcon"></i>
|
||||
</span>
|
||||
</span>
|
||||
<template v-if="bullets.length > 1">
|
||||
<span class="icon bullet" v-bind:key="bullet" v-for="bullet of bullets" @click="select(bullet)">
|
||||
<i v-if="bullet == index" class="fa fa-circle"></i>
|
||||
<i v-else class="far fa-circle"></i>
|
||||
</span>
|
||||
</template>
|
||||
<span class="right">
|
||||
<span class="icon bullet" @click="next()" v-if="showNext">
|
||||
<i :class="rightButtonIcon"></i>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<slot name="bullets-right" :v-bind="this"></slot>
|
||||
</nav>
|
||||
</section>
|
||||
</template>
|
||||
<style scoped>
|
||||
.a-carousel {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.a-carousel-viewport {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.a-carousel-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: left;
|
||||
}
|
||||
|
||||
.a-carousel-container > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.a-carousel-bullets-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.a-carousel-bullets-container .bullet {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.a-carousel-bullets-container .left {
|
||||
min-width: 2rem;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.a-carousel-bullets-container .right {
|
||||
min-width: 2rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.a-carousel-bullets-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import {ref} from 'vue'
|
||||
|
||||
|
||||
class Offset {
|
||||
constructor(el, min=null, max=null) {
|
||||
this.el = el
|
||||
this.rect = el.getBoundingClientRect();
|
||||
({min, max} = this.minmax(min, max))
|
||||
this.min = min
|
||||
this.max = max
|
||||
this.size = max-min
|
||||
}
|
||||
|
||||
minmax(min=null, max=null) {
|
||||
min = min === null ? this.rect.left : min
|
||||
max = max === null ? this.rect.right : max
|
||||
return {min, max}
|
||||
}
|
||||
|
||||
relative(to) {
|
||||
return new Offset(this.el, this.min-to.min, this.max-to.min)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Card extends Offset {
|
||||
constructor(el, index) {
|
||||
super(el)
|
||||
this.index = index
|
||||
}
|
||||
|
||||
visible(viewportOffset) {
|
||||
return viewportOffset.min <= this.min && viewportOffset.max >= this.max
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
viewport: ref(null),
|
||||
container: ref(null),
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cards: [],
|
||||
index: 0,
|
||||
refresh_: 0,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
cardSelector: {type: String, default: ''},
|
||||
containerClass: {type: String, default: ''},
|
||||
buttonClass: {type: String, default: 'button'},
|
||||
leftButtonIcon: {type: String, default: "fas fa-chevron-left"},
|
||||
rightButtonIcon: {type: String, default: "fas fa-chevron-right"},
|
||||
},
|
||||
|
||||
computed: {
|
||||
card() { return this.cards()[this.index] },
|
||||
|
||||
showPrev() {
|
||||
return this.index > 0
|
||||
},
|
||||
|
||||
showNext() {
|
||||
if(!this.cards || this.cards.length <= 1)
|
||||
return false
|
||||
|
||||
let last = this.bullets[this.bullets.length-1]
|
||||
return this.index != last
|
||||
},
|
||||
|
||||
bullets() {
|
||||
if(!this.cards || !this.$refs.viewport)
|
||||
return []
|
||||
|
||||
let contOff = new Offset(this.$refs.container)
|
||||
let viewMax = new Offset(this.$refs.viewport).size
|
||||
let bullets = []
|
||||
|
||||
let i = 0;
|
||||
let max = viewMax
|
||||
bullets.push(i)
|
||||
while(i < this.cards.length) {
|
||||
// skip until next view
|
||||
for(; i < this.cards.length; i++) {
|
||||
let card = this.cards[i].relative(contOff)
|
||||
if(card.max > max) {
|
||||
max = card.min + viewMax
|
||||
bullets.push(i)
|
||||
i++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return bullets
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCards() {
|
||||
if(!this.$refs.container)
|
||||
return []
|
||||
let nodes = (!this.cardSelector) ?
|
||||
[...this.$refs.container.children] :
|
||||
[...this.$refs.container.querySelectorAll(this.cardSelector)]
|
||||
return nodes.map((el, index) => new Card(el, index))
|
||||
},
|
||||
|
||||
select(index, relative=false) {
|
||||
if(relative)
|
||||
index = this.index + index
|
||||
|
||||
index = Math.min(index, this.cards.length)
|
||||
index = Math.max(index, 0)
|
||||
let card = this.cards[index]
|
||||
if(!card)
|
||||
return null;
|
||||
|
||||
card = new Card(card.el)
|
||||
const cont = new Offset(this.$refs.container)
|
||||
const rel = card.relative(cont)
|
||||
this.$refs.container.style.marginLeft = `-${rel.min}px`
|
||||
this.index = index;
|
||||
return card.el
|
||||
},
|
||||
|
||||
next() {
|
||||
let n = this.bullets.indexOf(this.index)
|
||||
let index = this.bullets[n+1]
|
||||
this.select(index)
|
||||
},
|
||||
|
||||
prev() {
|
||||
let n = this.bullets.indexOf(this.index)
|
||||
let index = this.bullets[n-1]
|
||||
this.select(index)
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.cards = this.getCards()
|
||||
this.select(this.index)
|
||||
this.refresh_++
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted() {
|
||||
this.observers = [
|
||||
new MutationObserver(() => this.refresh()),
|
||||
new ResizeObserver(() => this.refresh())
|
||||
]
|
||||
this.observers[0].observe(this.$refs.container, {"childList": true})
|
||||
this.observers[1].observe(this.$refs.container)
|
||||
this.refresh()
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
for(var observer of this.observers)
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,49 +0,0 @@
|
|||
<template>
|
||||
<component :is="tag" :class="[itemClass, active ? activeClass : '']">
|
||||
<slot name="before-button" :toggle="toggle" :active="active"></slot>
|
||||
<slot name="button" :toggle="toggle" :active="active">
|
||||
<component :is="buttonTag" :class="buttonClass" @click="toggle()">
|
||||
<span class="icon" v-if="labelIcon">
|
||||
<i :class="labelIcon"></i>
|
||||
</span>
|
||||
<span>{{ label }}</span>
|
||||
<span class="icon">
|
||||
<i v-if="!active" :class="buttonIcon"></i>
|
||||
<i v-if="active" :class="buttonIconClose"></i>
|
||||
</span>
|
||||
</component>
|
||||
</slot>
|
||||
<div :class="contentClass" v-show="active">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
active: this.open,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
tag: {type: String, default: "div"},
|
||||
label: {type: String, default: ""},
|
||||
labelIcon: {type: String, default: ""},
|
||||
buttonTag: {type: String, default: "button"},
|
||||
activeClass: {type: String, default: "is-active"},
|
||||
buttonClass: {type: String, default: "button"},
|
||||
buttonIcon: { type: String, default:"fa fa-angle-down"},
|
||||
buttonIconClose: { type: String, default:"fa fa-angle-up"},
|
||||
contentClass: String,
|
||||
open: {type: Boolean, default: false},
|
||||
noButton: {type: Boolean, default: false},
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
this.active = !this.active
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,132 +0,0 @@
|
|||
<template>
|
||||
<input ref="input" type="hidden" :name="name" :value="value"/>
|
||||
<div class="">
|
||||
<template v-for="group, index in menu" :key="index">
|
||||
<div class="button-group d-inline-block mr-3">
|
||||
<template v-for="info, index in group" :key="index">
|
||||
<button type="button" class="button square smaller" :title="info.label" @click="edit(info.action, ...(info.args || []))">
|
||||
<span class="icon"><i :class="info.icon"/></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div class="button-group d-inline-block">
|
||||
<div class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<button type="button" class="button square smaller">
|
||||
<span class="icon"><i class="fa fa-link"/></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" style="min-width: 20rem; margin-top: -0.2rem;">
|
||||
<div class="dropdown-content p-3">
|
||||
<div class="field">
|
||||
<label class="label">Lien</label>
|
||||
<div class="control">
|
||||
<input ref="link-url" type="text" class="input" placeholder="lien"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="has-text-right">
|
||||
<button type="button" class="button secondary"
|
||||
@click="edit('setLink', {href:$refs['link-url'].value})">
|
||||
Ajouter le lien
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="button square smaller" title="Remove link" @click="edit('unsetLink')">
|
||||
<span class="icon"><i class="fa fa-link-slash"/></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<editor-content class="editor" v-if="editor" :editor="editor" />
|
||||
</template>
|
||||
<style>
|
||||
.editor .tiptap {
|
||||
border: 1px black solid;
|
||||
padding: 0.3em;
|
||||
}
|
||||
.editor .tiptap ul, .editor .tiptap ol {
|
||||
margin-left: 1.3em;
|
||||
}
|
||||
|
||||
.editor .tiptap ul { list-style: disc }
|
||||
</style>
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Underline from '@tiptap/extension-underline'
|
||||
import Link from '@tiptap/extension-link'
|
||||
|
||||
export default {
|
||||
components: {EditorContent},
|
||||
props: {
|
||||
config: {type: Object, default: (() => {})},
|
||||
//! Input field name.
|
||||
name: String,
|
||||
//! Initial input value
|
||||
initial: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
menu: [
|
||||
[
|
||||
{label: "Bold", icon: "fa fa-bold", action: "toggleBold" },
|
||||
{label: "Italic", icon: "fa fa-italic", action: "toggleItalic" },
|
||||
{label: "Underline", icon: "fa fa-underline", action: "toggleUnderline" },
|
||||
{label: "Strike", icon: "fa fa-strikethrough", action: "toggleStrike" },
|
||||
],[
|
||||
{label: "List", icon: "fa fa-list", action: "toggleBulletList" },
|
||||
{label: "Ordered List", icon: "fa fa-list-ol", action: "toggleOrderedList" },
|
||||
],[
|
||||
{label: "Heading 1", icon: "fa fa-h", action: "setHeading", args: [{level:3}] },
|
||||
{label: "Heading 2", icon: "fa fa-h smaller", action: "toggleHeading", args: [{level:4}] },
|
||||
// {label: "Heading 3", icon: "fa fa-h small", action: "toggleHeading", args: [{level:5}] },
|
||||
],
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
value() { return this.editor && this.editor.getHTML() },
|
||||
},
|
||||
|
||||
methods: {
|
||||
chain(action, ...args) {
|
||||
let chain = this.editor.chain().focus()
|
||||
return chain[action](...args)
|
||||
},
|
||||
|
||||
edit(action, ...args) {
|
||||
this.chain(action, ...args).run()
|
||||
},
|
||||
|
||||
setLink() {
|
||||
this.edit("setLink", {href: this.$refs['link-url']})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
content: this.initial || "",
|
||||
injectCss: false,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
heading: {
|
||||
levels: [3, 4, 5]
|
||||
}
|
||||
}),
|
||||
Underline,
|
||||
Link.configure({autolink: true}),
|
||||
],
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
beforeUnmount() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,19 +0,0 @@
|
|||
<template>
|
||||
<slot :page="page" :podcasts="podcasts"></slot>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Set} from '../model.js';
|
||||
import Sound from '../sound.js';
|
||||
import APage from './APage.vue';
|
||||
|
||||
export default {
|
||||
extends: APage,
|
||||
|
||||
data() {
|
||||
return {
|
||||
podcasts: new Set(Sound, {items:this.page.podcasts}),
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,110 +0,0 @@
|
|||
<template>
|
||||
<div ref="list" class="a-select-file-list">
|
||||
<form ref="form" class="flex-column" v-if="state == STATE.DEFAULT">
|
||||
<slot name="form"></slot>
|
||||
<div class="field is-horizontal">
|
||||
<label class="label">{{ label }}</label>
|
||||
<input type="file" ref="uploadFile" :name="fieldName" @change="onFileChange"/>
|
||||
</div>
|
||||
<div class="flex-row align-right" v-if="submitLabel">
|
||||
<button type="button" class="button small" @click="submit">
|
||||
{{ submitLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="flex-column" v-else>
|
||||
<slot name="preview" :fileUrl="fileUrl" :file="file" :loaded="loaded" :total="total"></slot>
|
||||
<div class="flex-row">
|
||||
<progress :max="total" :value="loaded"/>
|
||||
<button type="button" class="button small square ml-2" @click="abort">
|
||||
<span class="icon small">
|
||||
<i class="fa fa-close"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {getCsrf} from "../model.js"
|
||||
|
||||
export default {
|
||||
emit: ["fileChange", "load", "abort", "error"],
|
||||
|
||||
props: {
|
||||
url: { type: String },
|
||||
fieldName: { type: String, default: "file" },
|
||||
label: { type: String, default: "Select a file" },
|
||||
submitLabel: { type: String, default: "Upload" },
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
STATE: {
|
||||
DEFAULT: 0,
|
||||
UPLOADING: 1,
|
||||
},
|
||||
state: 0,
|
||||
upload: {},
|
||||
file: null,
|
||||
fileUrl: null,
|
||||
total: 0,
|
||||
loaded: 0,
|
||||
request: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
abort() {
|
||||
this.request && this.request.abort()
|
||||
},
|
||||
|
||||
onFileChange() {
|
||||
const [file] = this.$refs.uploadFile.files
|
||||
if(!file)
|
||||
return
|
||||
this._setUploadFile(file)
|
||||
this.$emit("fileChange", {upload: this, file: this.file, fileUrl: this.fileUrl})
|
||||
},
|
||||
|
||||
submit() {
|
||||
const req = new XMLHttpRequest()
|
||||
req.open("POST", this.url)
|
||||
req.upload.addEventListener("progress", (e) => this.onUploadProgress(e))
|
||||
req.addEventListener("load", (e) => this.onUploadDone(e, 'load'))
|
||||
req.addEventListener("abort", (e) => this.onUploadDone(e, 'abort'))
|
||||
req.addEventListener("error", (e) => this.onUploadDone(e, 'error'))
|
||||
|
||||
const formData = new FormData(this.$refs.form);
|
||||
formData.append('csrfmiddlewaretoken', getCsrf())
|
||||
req.send(formData)
|
||||
|
||||
this._resetUpload(this.STATE.UPLOADING, false, req)
|
||||
},
|
||||
|
||||
onUploadProgress(event) {
|
||||
this.loaded = event.loaded
|
||||
this.total = event.total
|
||||
},
|
||||
|
||||
onUploadDone(event, eventName) {
|
||||
this.$emit(eventName, event)
|
||||
this._resetUpload(this.STATE.DEFAULT, true)
|
||||
},
|
||||
|
||||
_setUploadFile(file) {
|
||||
this.file = file
|
||||
this.fileURL = file && URL.createObjectURL(file)
|
||||
},
|
||||
|
||||
_resetUpload(state, resetFile=false, request=null) {
|
||||
this.state = state
|
||||
this.loaded = 0
|
||||
this.total = 0
|
||||
this.request = request
|
||||
if(resetFile)
|
||||
this.file = null
|
||||
}
|
||||
|
||||
},}
|
||||
</script>
|
|
@ -1,193 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<input type="hidden" :name="_prefix + 'TOTAL_FORMS'" :value="items.length || 0"/>
|
||||
<template v-for="(value,name) in formData.management" v-bind:key="name">
|
||||
<input type="hidden" :name="_prefix + name.toUpperCase()"
|
||||
:value="value"/>
|
||||
</template>
|
||||
|
||||
<a-rows ref="rows" :set="set" :context="this"
|
||||
:columns="visibleFields" :columnsOrderable="columnsOrderable"
|
||||
:orderable="orderable" @move="moveItem" @colmove="onColumnMove"
|
||||
@cell="e => $emit('cell', e)">
|
||||
|
||||
<template #header-head>
|
||||
<template v-if="orderable">
|
||||
<th style="max-width:2em" :title="orderField.label"
|
||||
:aria-label="orderField.label"
|
||||
:aria-description="orderField.help || ''">
|
||||
<span class="icon">
|
||||
<i class="fa fa-arrow-down-1-9"></i>
|
||||
</span>
|
||||
</th>
|
||||
<slot name="rows-header-head"></slot>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #row-head="data">
|
||||
<input v-if="orderable" type="hidden"
|
||||
:name="_prefix + data.row + '-' + orderBy"
|
||||
:value="data.row"/>
|
||||
<input type="hidden" :name="_prefix + data.row + '-id'"
|
||||
:value="data.item ? data.item.id : ''"/>
|
||||
|
||||
<template v-for="field of hiddenFields" v-bind:key="field.name">
|
||||
<input type="hidden"
|
||||
v-if="!(field.name in ['id', orderBy])"
|
||||
:name="_prefix + data.row + '-' + field.name"
|
||||
:value="field.value in [null, undefined] ? data.item.data[name] : field.value"/>
|
||||
</template>
|
||||
|
||||
<slot name="row-head" v-bind="data">
|
||||
<td v-if="orderable">{{ data.row+1 }}</td>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<template v-for="(field,slot) of fieldSlots" v-bind:key="field.name"
|
||||
v-slot:[slot]="data">
|
||||
<slot :name="slot" v-bind="data" :field="field" :input-name="_prefix + data.cell.row + '-' + field.name">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<slot :name="'control-' + field.name" v-bind="data" :field="field" :input-name="_prefix + data.cell.row + '-' + field.name"/>
|
||||
</div>
|
||||
<p v-for="[error,index] in data.item.error(field.name)" class="help is-danger" v-bind:key="index">
|
||||
{{ error }}
|
||||
</p>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<template #row-tail="data">
|
||||
<slot v-if="$slots['row-tail']" name="row-tail" v-bind="data"/>
|
||||
<td class="align-right pr-0">
|
||||
<button type="button" class="button square"
|
||||
@click.stop="removeItem(data.row, data.item)"
|
||||
:title="labels.remove_item"
|
||||
:aria-label="labels.remove_item">
|
||||
<span class="icon"><i class="fa fa-trash" /></span>
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
</a-rows>
|
||||
<div class="a-formset-footer flex-row">
|
||||
<div class="flex-grow-1 flex-row">
|
||||
<slot name="footer"/>
|
||||
</div>
|
||||
<div class="flex-grow-1 align-right">
|
||||
<button type="button" class="button square is-warning p-2"
|
||||
@click="reset()"
|
||||
:title="labels.discard_changes"
|
||||
:aria-label="labels.discard_changes"
|
||||
>
|
||||
<span class="icon"><i class="fa fa-rotate" /></span>
|
||||
</button>
|
||||
<button type="button" class="button square is-primary p-2"
|
||||
@click="onActionAdd"
|
||||
:title="labels.add_item"
|
||||
:aria-label="labels.add_item"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fa fa-plus"/></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {cloneDeep} from 'lodash'
|
||||
import Model, {Set} from '../model'
|
||||
|
||||
import ARows from './ARows'
|
||||
|
||||
export default {
|
||||
emit: ['cell', 'move', 'colmove', 'load'],
|
||||
components: {ARows},
|
||||
|
||||
props: {
|
||||
labels: Object,
|
||||
|
||||
//! If provided call this function instead of adding an item to rows on "+" button click.
|
||||
actionAdd: Function,
|
||||
|
||||
//! If True, columns can be reordered
|
||||
columnsOrderable: Boolean,
|
||||
//! Field name used for ordering
|
||||
orderBy: String,
|
||||
|
||||
//! Formset data as returned by get_formset_data
|
||||
formData: Object,
|
||||
//! Model class used for item's set
|
||||
model: {type: Function, default: Model},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
set: new Set(Model),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
// ---- fields
|
||||
_prefix() { return this.formData.prefix ? this.formData.prefix + '-' : '' },
|
||||
fields() { return this.formData.fields },
|
||||
orderField() { return this.orderBy && this.fields.find(f => f.name == this.orderBy) },
|
||||
orderable() { return !!this.orderField },
|
||||
|
||||
hiddenFields() { return this.fields.filter(f => f.hidden && !(this.orderable && f == this.orderField)) },
|
||||
visibleFields() { return this.fields.filter(f => !f.hidden) },
|
||||
|
||||
fieldSlots() { return this.visibleFields.reduce(
|
||||
(slots, f) => ({...slots, ['row-' + f.name]: f}),
|
||||
{}
|
||||
)},
|
||||
|
||||
items() { return this.set.items },
|
||||
rows() { return this.$refs.rows },
|
||||
},
|
||||
|
||||
methods: {
|
||||
onCellEvent(event) { this.$emit('cell', event) },
|
||||
onColumnMove(event) { this.$emit('colmove', event) },
|
||||
onActionAdd() {
|
||||
if(this.actionAdd)
|
||||
return this.actionAdd(this)
|
||||
this.set.push()
|
||||
},
|
||||
|
||||
moveItem(event) {
|
||||
const {from, to} = event
|
||||
const set_ = event.set || this.set
|
||||
set_.move(from, to);
|
||||
this.$emit('move', {...event, seŧ: set_})
|
||||
},
|
||||
|
||||
removeItem(row) {
|
||||
const item = this.items[row]
|
||||
if(item.id) {
|
||||
// TODO
|
||||
}
|
||||
else {
|
||||
this.items.splice(row,1)
|
||||
}
|
||||
},
|
||||
|
||||
//! Load items into set
|
||||
load(items=[], reset=false) {
|
||||
if(reset)
|
||||
this.set.items = []
|
||||
for(var item of items)
|
||||
this.set.push(cloneDeep(item))
|
||||
this.$emit('load', items)
|
||||
},
|
||||
|
||||
//! Reset forms to initials
|
||||
reset() {
|
||||
this.load(this.formData?.initials || [], true)
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,105 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- FIXME: header and footer should be inside list tags -->
|
||||
<slot name="header"></slot>
|
||||
<component :is="listTag" :class="listClass">
|
||||
<template v-for="(item,index) in items" :key="index">
|
||||
<component :is="itemTag" :class="itemClass" @click="select(index)"
|
||||
:draggable="orderable" :data-index="index"
|
||||
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
|
||||
<slot name="item" :selected="index == selectedIndex" :set="set" :index="index" :item="item"></slot>
|
||||
</component>
|
||||
</template>
|
||||
</component>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
emits: ['select', 'unselect', 'move', 'remove'],
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: this.defaultIndex,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
listClass: String,
|
||||
itemClass: String,
|
||||
defaultIndex: { type: Number, default: -1},
|
||||
set: Object,
|
||||
orderable: { type: Boolean, default: false },
|
||||
itemTag: { default: 'li' },
|
||||
listTag: { default: 'ul' },
|
||||
},
|
||||
|
||||
computed: {
|
||||
model() { return this.set.model },
|
||||
items() { return this.set.items },
|
||||
length() { return this.set.length },
|
||||
|
||||
selected() {
|
||||
return this.selectedIndex > -1 && this.items.length > this.selectedIndex > -1
|
||||
? this.items[this.selectedIndex] : null;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
get(index) { return this.set.get(index) },
|
||||
find(pred) { return this.set.find(pred) },
|
||||
findIndex(pred) { return this.set.findIndex(pred) },
|
||||
|
||||
remove(index, select=false) {
|
||||
const item = this.set.get(index)
|
||||
if(!item)
|
||||
return
|
||||
|
||||
this.set.remove(index);
|
||||
if(index < this.selectedIndex)
|
||||
this.selectedIndex--;
|
||||
if(select && this.selectedIndex == index)
|
||||
this.select(index)
|
||||
this.$emit('remove', {index, item, set: this.set})
|
||||
},
|
||||
|
||||
select(index) {
|
||||
this.selectedIndex = index > -1 && this.items.length ? index % this.items.length : -1;
|
||||
this.$emit('select', { item: this.selected, index: this.selectedIndex });
|
||||
return this.selectedIndex;
|
||||
},
|
||||
|
||||
unselect() {
|
||||
this.$emit('unselect', { item: this.selected, index: this.selectedIndex});
|
||||
this.selectedIndex = -1;
|
||||
},
|
||||
|
||||
onDragStart(ev) {
|
||||
const dataset = ev.target.dataset;
|
||||
const data = `row:${dataset.index}`
|
||||
ev.dataTransfer.setData("text/cell", data)
|
||||
ev.dataTransfer.dropEffect = 'move'
|
||||
},
|
||||
|
||||
onDragOver(ev) {
|
||||
ev.preventDefault()
|
||||
ev.dataTransfer.dropEffect = 'move'
|
||||
},
|
||||
|
||||
onDrop(ev) {
|
||||
const data = ev.dataTransfer.getData("text/cell")
|
||||
if(!data || !data.startsWith('row:'))
|
||||
return
|
||||
|
||||
ev.preventDefault()
|
||||
const from = Number(data.slice(4))
|
||||
const target = ev.target.tagName == this.itemTag ? ev.target
|
||||
: ev.target.closest(this.itemTag)
|
||||
this.$emit('move', {
|
||||
from, target,
|
||||
to: Number(target.dataset.index),
|
||||
set: this.set,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,109 +0,0 @@
|
|||
<template>
|
||||
<div class="a-m2m-edit">
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<slot name="items-title"></slot>
|
||||
</th>
|
||||
<th style="width: 1rem">
|
||||
<span class="icon">
|
||||
<i class="fa fa-trash"/>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="item of items" :key="item.id">
|
||||
<tr :class="[item.created && 'has-text-info', item.deleted && 'has-text-danger']">
|
||||
<td>
|
||||
<slot name="item" :item="item">
|
||||
{{ item.data }}
|
||||
</slot>
|
||||
</td>
|
||||
<td class="align-center">
|
||||
<input type="checkbox" class="checkbox" @change="item.deleted = $event.target.checked">
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<label>
|
||||
<span class="icon">
|
||||
<i class="fa fa-plus"/>
|
||||
</span>
|
||||
Add
|
||||
</label>
|
||||
<a-autocomplete ref="autocomplete" v-bind="autocomplete"
|
||||
@select="onSelect">
|
||||
<template #item="{item}">
|
||||
<slot name="autocomplete-item" :item="item">{{ item }}</slot>
|
||||
</template>
|
||||
</a-autocomplete>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Model, { Set } from "../model.js"
|
||||
import AAutocomplete from "./AAutocomplete.vue"
|
||||
|
||||
export default {
|
||||
components: {AAutocomplete},
|
||||
props: {
|
||||
model: {type: Function, default: Model },
|
||||
// List url
|
||||
url: String,
|
||||
// POST url
|
||||
commitUrl: String,
|
||||
// v-bind to autocomplete search box
|
||||
autocomplete: {type: Object },
|
||||
|
||||
source_id: Number,
|
||||
source_field: String,
|
||||
target_field: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
set: new Set(this.model, {url: this.url, unique: true}),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
items() { return this.set?.items || [] },
|
||||
initials() {
|
||||
let obj = {}
|
||||
obj[this.source_id_attr] = this.source_id
|
||||
return obj
|
||||
},
|
||||
|
||||
source_id_attr() { return this.source_field + "_id" },
|
||||
target_id_attr() { return this.target_field + "_id" },
|
||||
target_ids() { return this.set?.items.map(i => i.data[this.target_id_attr]) },
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(index, item, value) {
|
||||
if(this.target_ids.indexOf(item.id) != -1)
|
||||
return
|
||||
|
||||
let obj = {...this.initials}
|
||||
obj[this.target_field] = {...item}
|
||||
obj[this.target_id_attr] = item.id
|
||||
this.set.push(obj)
|
||||
this.$refs.autocomplete.reset()
|
||||
},
|
||||
|
||||
save() {
|
||||
this.set.commit(this.commitUrl, {
|
||||
fields: [...Object.keys(this.initials), this.target_id_attr]
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.set.fetch()
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,53 +0,0 @@
|
|||
<template>
|
||||
<section :class="['modal', active && 'is-active' || '']">
|
||||
<div class="modal-background" @click="close"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<div class="modal-card-title">
|
||||
<slot name="title" :item="item">{{ title }}</slot>
|
||||
</div>
|
||||
<slot name="bar" :item="item"></slot>
|
||||
<button type="button" class="delete square" aria-label="close" @click="close">
|
||||
<span class="icon">
|
||||
<i class="fa fa-close"></i>
|
||||
</span>
|
||||
</button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<slot name="default" :item="item"></slot>
|
||||
</section>
|
||||
<div class="modal-card-foot align-right">
|
||||
<slot name="footer" :item="item" :close="close"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, default: ""},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
///! If true, modal is open
|
||||
active: false,
|
||||
///! Item or data passed down to slots.
|
||||
item: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
///! Open modal dialog. Set provided `item` to dialog's one.
|
||||
open(item=null) {
|
||||
this.active = true
|
||||
this.item = item
|
||||
},
|
||||
///! Close modal and reset item to null.
|
||||
close() {
|
||||
this.active = false
|
||||
this.item = null
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,18 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
props: {
|
||||
page: Object,
|
||||
title: String,
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,283 +0,0 @@
|
|||
<template>
|
||||
<div class="a-player">
|
||||
<div :class="['a-player-panels', panel ? 'is-open' : '']">
|
||||
<template v-for="(info, key) in playlists" v-bind:key="key">
|
||||
<APlaylist
|
||||
:ref="key" class="a-player-panel a-playlist"
|
||||
v-show="panel == key && sets[key].length"
|
||||
:actions="['page', key != 'pin' && 'pin' || '']"
|
||||
:editable="true" :player="self" :set="sets[key]"
|
||||
@select="togglePlay(key, $event.index)"
|
||||
listClass="menu-list" itemClass="menu-item">
|
||||
<template v-slot:header="">
|
||||
<div class="title is-flex-grow-1">
|
||||
<span class="icon">
|
||||
<i :class="info[1]"></i>
|
||||
</span>
|
||||
{{ info[0] }}
|
||||
</div>
|
||||
<button class="action button no-border">
|
||||
<span class="icon" @click.stop="togglePanel()">
|
||||
<i class="fa fa-close"></i>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
</APlaylist>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="a-player-progress" v-if="loaded && duration">
|
||||
<AProgress v-if="loaded && duration" :value="currentTime" :max="this.duration"
|
||||
:format="displayTime"
|
||||
@select="audio.currentTime = $event"></AProgress>
|
||||
</div>
|
||||
<div class="a-player-bar button-group">
|
||||
<button class="button" @click="togglePlay()"
|
||||
:title="buttonTitle" :aria-label="buttonTitle">
|
||||
<span class="fas fa-pause" v-if="playing"></span>
|
||||
<span class="fas fa-play" v-else></span>
|
||||
</button>
|
||||
<div :class="['a-player-bar-content', loaded && duration ? 'has-progress' : '']">
|
||||
<slot name="content" :loaded="loaded" :live="live" :current="current"></slot>
|
||||
</div>
|
||||
<button class="button has-text-weight-bold" v-if="loaded" @click="play()"
|
||||
title="Live">
|
||||
<span class="icon is-size-6 has-text-danger">
|
||||
<span class="fa fa-circle"></span>
|
||||
</span>
|
||||
</button>
|
||||
<template v-if="sets">
|
||||
<template v-for="(info, key) in playlists" v-bind:key="key">
|
||||
<button :class="playlistButtonClass(key)"
|
||||
@click="togglePanel(key)"
|
||||
v-show="sets[key] && sets[key].length">
|
||||
<span class="is-size-6">{{ sets[key] && sets[key].length }}</span>
|
||||
<span class="icon">
|
||||
<i :class="info[1]"></i>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {reactive} from 'vue'
|
||||
import Live from '../live'
|
||||
import Sound from '../sound'
|
||||
import {Set} from '../model'
|
||||
import APlaylist from './APlaylist'
|
||||
import AProgress from './AProgress'
|
||||
|
||||
|
||||
export const State = {
|
||||
paused: 0,
|
||||
playing: 1,
|
||||
loading: 2,
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { APlaylist, AProgress },
|
||||
|
||||
data() {
|
||||
let audio = new Audio();
|
||||
audio.addEventListener('ended', e => this.onState(e));
|
||||
audio.addEventListener('pause', e => this.onState(e));
|
||||
audio.addEventListener('playing', e => this.onState(e));
|
||||
audio.addEventListener('timeupdate', () => {
|
||||
this.currentTime = this.audio.currentTime;
|
||||
});
|
||||
audio.addEventListener('durationchange', () => {
|
||||
this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;
|
||||
});
|
||||
|
||||
let live = this.liveArgs ? reactive(new Live(this.liveArgs)) : null;
|
||||
live && live.refresh();
|
||||
|
||||
const sets = {}
|
||||
for(const key in this.playlists)
|
||||
sets[key] = Set.storeLoad(Sound, 'playlist.' + key,
|
||||
{max: 30, unique: true})
|
||||
|
||||
return {
|
||||
audio, duration: 0, currentTime: 0, state: State.paused,
|
||||
live,
|
||||
|
||||
/// Loaded item
|
||||
loaded: null,
|
||||
//! Active panel name
|
||||
panel: null,
|
||||
//! current playing playlist name
|
||||
playlistName: null,
|
||||
//! players' playlists' sets
|
||||
sets,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
buttonTitle: String,
|
||||
liveArgs: Object,
|
||||
///! dict of {'slug': ['Label', 'icon']}
|
||||
playlists: Object,
|
||||
},
|
||||
|
||||
computed: {
|
||||
self() { return this; },
|
||||
paused() { return this.state == State.paused; },
|
||||
playing() { return this.state == State.playing; },
|
||||
loading() { return this.state == State.loading; },
|
||||
|
||||
playlist() {
|
||||
return this.playlistName ? this.$refs[this.playlistName][0] : null;
|
||||
},
|
||||
|
||||
current() {
|
||||
return this.loaded ? this.loaded : this.live && this.live.current;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
displayTime(seconds) {
|
||||
seconds = parseInt(seconds);
|
||||
let s = seconds % 60;
|
||||
seconds = (seconds - s) / 60;
|
||||
let m = seconds % 60;
|
||||
let h = (seconds - m) / 60;
|
||||
|
||||
let [ss,mm,hh] = [s.toString().padStart(2, '0'),
|
||||
m.toString().padStart(2, '0'),
|
||||
h.toString().padStart(2, '0')];
|
||||
return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;
|
||||
},
|
||||
|
||||
playlistButtonClass(name) {
|
||||
let set = this.sets[name];
|
||||
return (set ? (set.length ? "" : "has-text-grey-light ")
|
||||
+ (this.panel == name ? "open"
|
||||
: this.playlistName == name ? 'active' : '') : '')
|
||||
+ " button";
|
||||
},
|
||||
|
||||
/// Show/hide panel
|
||||
togglePanel(panel) { this.panel = this.panel == panel ? null : panel },
|
||||
/// Return True if item is loaded
|
||||
isLoaded(item) { return this.loaded && this.loaded.id == item.id },
|
||||
/// Return True if item is loaded
|
||||
isPlaying(item) { return this.isLoaded(item) && !this.paused },
|
||||
|
||||
_setPlaylist(playlist) {
|
||||
this.playlistName = playlist;
|
||||
for(var p in this.sets)
|
||||
if(p != playlist && this.$refs[p])
|
||||
this.$refs[p][0].unselect();
|
||||
},
|
||||
|
||||
/// Load a sound from playlist or live
|
||||
load(playlist=null, index=0) {
|
||||
let src = null;
|
||||
|
||||
// from playlist
|
||||
if(playlist !== null && index != -1) {
|
||||
let item = this.$refs[playlist][0].get(index);
|
||||
if(!item)
|
||||
throw `No sound at index ${index} for playlist ${playlist}`;
|
||||
this.loaded = item
|
||||
src = item.src;
|
||||
}
|
||||
// from live
|
||||
else {
|
||||
this.loaded = null;
|
||||
src = this.live.src;
|
||||
}
|
||||
|
||||
this._setPlaylist(playlist);
|
||||
|
||||
// load sources
|
||||
const audio = this.audio;
|
||||
if(src instanceof Array) {
|
||||
audio.innerHTML = '';
|
||||
audio.removeAttribute('src');
|
||||
for(var s of src) {
|
||||
let source = document.createElement('source');
|
||||
source.setAttribute('src', s);
|
||||
audio.appendChild(source)
|
||||
}
|
||||
}
|
||||
else {
|
||||
audio.src = src;
|
||||
}
|
||||
audio.load();
|
||||
},
|
||||
|
||||
play(playlist=null, index=0) {
|
||||
this.load(playlist, index);
|
||||
this.audio.play().catch(e => console.error(e))
|
||||
},
|
||||
|
||||
/// Push items to playlist (by name)
|
||||
push(playlist, ...items) {
|
||||
return this.sets[playlist].push(...items);
|
||||
},
|
||||
|
||||
/// Push and play items
|
||||
playItems(playlist, ...items) {
|
||||
let index = this.push(playlist, ...items);
|
||||
this.$refs[playlist][0].selectedIndex = index;
|
||||
this.play(playlist, index);
|
||||
},
|
||||
|
||||
/// Handle click event that plays multiple items (from `data-sounds` attribute)
|
||||
playButtonClick(event) {
|
||||
var items = JSON.parse(event.currentTarget.dataset.sounds);
|
||||
this.playItems('queue', ...items);
|
||||
},
|
||||
|
||||
/// Pause
|
||||
pause() {
|
||||
this.audio.pause()
|
||||
},
|
||||
|
||||
//! Play/pause
|
||||
togglePlay(playlist=null, index=0) {
|
||||
if(playlist !== null) {
|
||||
this.panel = null;
|
||||
let item = this.sets[playlist].get(index);
|
||||
if(!this.playlist || this.playlistName !== playlist || this.loaded != item) {
|
||||
this.play(playlist, index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(this.paused)
|
||||
this.audio.play().catch(e => console.error(e))
|
||||
else
|
||||
this.audio.pause();
|
||||
},
|
||||
|
||||
//! Pin/Unpin an item
|
||||
togglePlaylist(playlist, item) {
|
||||
const set = this.sets[playlist]
|
||||
let index = set.findIndex(item);
|
||||
if(index > -1)
|
||||
set.remove(index);
|
||||
else {
|
||||
set.push(item);
|
||||
// this.$refs.pinPlaylistButton.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/// Audio player state change event
|
||||
onState(event) {
|
||||
const audio = this.audio;
|
||||
this.state = audio.paused ? State.paused : State.playing;
|
||||
|
||||
if(event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1))
|
||||
this.play();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<div class="a-playlist">
|
||||
<div class="header"><slot name="header"></slot></div>
|
||||
<ul :class="listClass">
|
||||
<li v-for="(item,index) in items" :class="[itemClass, player.isPlaying(item) ? 'is-active' : '']" @click="!hasAction('play') && select(index)"
|
||||
:key="index">
|
||||
<ASoundItem
|
||||
:data="item" :index="index" :set="set" :player="player_"
|
||||
@togglePlay="togglePlay(index)"
|
||||
:actions="actions">
|
||||
<template #after-title="bindings">
|
||||
<slot name="after-title" v-bind="bindings"></slot>
|
||||
</template>
|
||||
<template #actions="bindings">
|
||||
<slot name="actions" v-bind="bindings"></slot>
|
||||
<button class="button" v-if="editable" @click.stop="remove(index,true)">
|
||||
<span class="icon is-small"><span class="fa fa-close"></span></span>
|
||||
</button>
|
||||
</template>
|
||||
</ASoundItem>
|
||||
</li>
|
||||
</ul>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AList from './AList';
|
||||
import ASoundItem from './ASoundItem';
|
||||
|
||||
export default {
|
||||
extends: AList,
|
||||
emits: [...AList.emits],
|
||||
components: { ASoundItem },
|
||||
|
||||
props: {
|
||||
actions: Array,
|
||||
// FIXME: remove
|
||||
name: String,
|
||||
player: Object,
|
||||
editable: Boolean,
|
||||
withLink: Boolean
|
||||
},
|
||||
|
||||
computed: {
|
||||
self() { return this; },
|
||||
player_() { return this.player || window.aircox.player },
|
||||
},
|
||||
|
||||
methods: {
|
||||
hasAction(action) { return this.actions && this.actions.indexOf(action) != -1; },
|
||||
|
||||
selectNext() {
|
||||
let index = this.selectedIndex + 1;
|
||||
return this.select(index >= this.items.length ? -1 : index);
|
||||
},
|
||||
|
||||
togglePlay(index) {
|
||||
if(this.player_.isPlaying(this.set.get(index)))
|
||||
this.player_.pause();
|
||||
else
|
||||
this.select(index)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,71 +0,0 @@
|
|||
<template>
|
||||
<div class="a-progress m-0">
|
||||
<time class="time-now">
|
||||
<slot name="value" :value="value" :max="max">{{ format(value) }}</slot>
|
||||
</time>
|
||||
<div ref="bar" class="a-progress-bar-container" @click.stop="onClick" @mouseleave.stop="onMouseMove"
|
||||
@mousemove.stop="onMouseMove">
|
||||
<div :class="progressClass" :style="progressStyle">
|
||||
<time v-if="hoverValue">
|
||||
{{ format(hoverValue) }}
|
||||
</time>
|
||||
<template v-else> </template>
|
||||
</div>
|
||||
</div>
|
||||
<time class="time-total">
|
||||
<slot name="value" :value="valueDisplay" :max="max">{{ format(max) }}</slot>
|
||||
</time>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
hoverValue: null,
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
value: Number,
|
||||
max: Number,
|
||||
format: { type: Function, default: x => x },
|
||||
progressClass: { default: 'a-progress-bar' },
|
||||
vertical: { type: Boolean, default: false },
|
||||
},
|
||||
|
||||
computed: {
|
||||
valueDisplay() { return this.hoverValue === null ? this.value : this.hoverValue; },
|
||||
|
||||
progressStyle() {
|
||||
if(!this.max)
|
||||
return null;
|
||||
let value = this.max ? this.valueDisplay * 100 / this.max : 0;
|
||||
return this.vertical ? { height: `${value}%` } : { width: `${value}%` };
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
xToValue(x) { return x * this.max / this.$refs.bar.getBoundingClientRect().width },
|
||||
yToValue(y) { return y * this.max / this.$refs.bar.getBoundingClientRect().height },
|
||||
|
||||
valueFromEvent(event) {
|
||||
let rect = event.currentTarget.getBoundingClientRect()
|
||||
return this.vertical ? this.yToValue(event.clientY - rect.y)
|
||||
: this.xToValue(event.clientX - rect.x);
|
||||
},
|
||||
|
||||
onClick(event) {
|
||||
this.$emit('select', this.valueFromEvent(event));
|
||||
},
|
||||
|
||||
onMouseMove(event) {
|
||||
if(event.type == 'mouseleave')
|
||||
this.hoverValue = null;
|
||||
else {
|
||||
this.hoverValue = this.valueFromEvent(event);
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,151 +0,0 @@
|
|||
<template>
|
||||
<tr>
|
||||
<slot name="head" :context="context" :item="item" :row="row"/>
|
||||
<template v-for="(attr,col) in columns" :key="col">
|
||||
<slot name="cell-before" :context="context" :item="item" :cell="cells[col]"
|
||||
:attr="attr"/>
|
||||
<component :is="cellTag" :class="['cell', 'cell-' + attr]" :data-col="col"
|
||||
:draggable="orderable"
|
||||
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop">
|
||||
<slot :name="attr" :context="context" :item="item" :cell="cells[col]"
|
||||
:data="itemData" :attr="attr" :emit="cellEmit"
|
||||
:value="itemData && itemData[attr]">
|
||||
{{ itemData && itemData[attr] }}
|
||||
</slot>
|
||||
<slot name="cell" :context="context" :item="item" :cell="cells[col]"
|
||||
:data="itemData" :attr="attr" :emit="cellEmit"
|
||||
:value="itemData && itemData[attr]"/>
|
||||
</component>
|
||||
<slot name="cell-after" :context="context" :item="item" :col="col" :cell="cells[col]"
|
||||
:attr="attr"/>
|
||||
</template>
|
||||
<slot name="tail" :context="context" :item="item" :row="row"/>
|
||||
</tr>
|
||||
</template>
|
||||
<script>
|
||||
import {isReactive, toRefs} from 'vue'
|
||||
import Model from '../model'
|
||||
|
||||
export default {
|
||||
emits: ['move', 'cell'],
|
||||
|
||||
props: {
|
||||
//! Context object
|
||||
context: {type: Object, default: () => ({})},
|
||||
//! Item to display in row
|
||||
item: {type: Object, default: () => ({})},
|
||||
//! Columns to display, as items' attributes
|
||||
//! - name: field name / item attribute value
|
||||
//! - label: display label
|
||||
//! - help: help text
|
||||
columns: Array,
|
||||
//! Default cell's info
|
||||
cell: {type: Object, default() { return {row: 0}}},
|
||||
//! Cell component tag
|
||||
cellTag: {type: String, default: 'td'},
|
||||
//! If true, can reorder cell by drag & drop
|
||||
orderable: {type: Boolean, default: false},
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Row index
|
||||
*/
|
||||
row() { return this.cell && this.cell.row || 0 },
|
||||
|
||||
/**
|
||||
* Item's data if model instance, otherwise item
|
||||
*/
|
||||
itemData() {
|
||||
return this.item instanceof Model ? this.item.data : this.item;
|
||||
},
|
||||
|
||||
/**
|
||||
* Computed cell infos
|
||||
*/
|
||||
cells() {
|
||||
const cell = isReactive(this.cell) && toRefs(this.cell) || this.cell || {}
|
||||
const cells = []
|
||||
for(var col in this.columns)
|
||||
cells.push({...cell, col: Number(col)})
|
||||
return cells
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Emit a 'cell' event.
|
||||
* Event data: `{name, cell, data, item}`
|
||||
* @param {Number} col: cell column's index
|
||||
* @param {String} name: cell's event name
|
||||
* @param {} data: cell's event data
|
||||
*/
|
||||
cellEmit(name, cell, data) {
|
||||
this.$emit('cell', {
|
||||
name, cell, data,
|
||||
item: this.item,
|
||||
})
|
||||
},
|
||||
|
||||
onDragStart(ev) {
|
||||
const dataset = ev.target.dataset;
|
||||
const data = `cell:${dataset.col}`
|
||||
ev.dataTransfer.setData("text/cell", data)
|
||||
ev.dataTransfer.dropEffect = 'move'
|
||||
},
|
||||
|
||||
onDragOver(ev) {
|
||||
ev.preventDefault()
|
||||
ev.dataTransfer.dropEffect = 'move'
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle drop event, emit `'move': { from, to }`.
|
||||
*/
|
||||
onDrop(ev) {
|
||||
const data = ev.dataTransfer.getData("text/cell")
|
||||
if(!data || !data.startsWith('cell:'))
|
||||
return
|
||||
|
||||
ev.preventDefault()
|
||||
this.$emit('move', {
|
||||
from: Number(data.slice(5)),
|
||||
to: Number(ev.target.dataset.col),
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return DOM node for cells at provided position `col`
|
||||
*/
|
||||
getCellEl(col) {
|
||||
const els = this.$el.querySelectorAll(this.cellTag)
|
||||
for(var el of els)
|
||||
if(col == Number(el.dataset.col))
|
||||
return el;
|
||||
return null
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus cell's form input. If from is provided, related focus
|
||||
*/
|
||||
focus(col, from) {
|
||||
if(from)
|
||||
col += from.col
|
||||
|
||||
const target = this.getCellEl(col)
|
||||
if(!target)
|
||||
return
|
||||
const control = target.querySelector('input:not([type="hidden"])') ||
|
||||
target.querySelector('button') ||
|
||||
target.querySelector('select') ||
|
||||
target.querySelector('a');
|
||||
control && control.focus()
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$el.__row = this
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
|
@ -1,153 +0,0 @@
|
|||
<template>
|
||||
<table class="table is-stripped is-fullwidth">
|
||||
<thead>
|
||||
<a-row :context="context" :columns="columnNames"
|
||||
:orderable="columnsOrderable" cellTag="th"
|
||||
@move="moveColumn">
|
||||
<template v-if="$slots['header-head']" v-slot:head="data">
|
||||
<slot name="header-head" v-bind="data"/>
|
||||
</template>
|
||||
<template v-if="$slots['header-tail']" v-slot:tail="data">
|
||||
<slot name="header-tail" v-bind="data"/>
|
||||
</template>
|
||||
<template v-for="column of columns" v-bind:key="column.name"
|
||||
v-slot:[column.name]="data">
|
||||
<slot :name="'header-' + column.name" v-bind="data">
|
||||
{{ column.label }}
|
||||
<span v-if="column.help" class="icon small"
|
||||
:title="column.help">
|
||||
<i class="fa fa-circle-question"/>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</a-row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<slot name="head"/>
|
||||
<template v-for="(item,row) in items" :key="row">
|
||||
<!-- data-index comes from AList component drag & drop -->
|
||||
<a-row :context="context" :item="item" :cell="{row}" :columns="columnNames" :data-index="row"
|
||||
:data-row="row"
|
||||
:draggable="orderable"
|
||||
@dragstart="onDragStart" @dragover="onDragOver" @drop="onDrop"
|
||||
@cell="onCellEvent(row, $event)">
|
||||
<template v-for="[name,slot] of rowSlots" :key="slot" v-slot:[slot]="data">
|
||||
<slot :name="name" v-bind="data"/>
|
||||
</template>
|
||||
</a-row>
|
||||
</template>
|
||||
<slot name="tail"/>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<script>
|
||||
import AList from './AList.vue'
|
||||
import ARow from './ARow.vue'
|
||||
|
||||
const Component = {
|
||||
extends: AList,
|
||||
components: { ARow },
|
||||
//! Event:
|
||||
//! - cell(event): an event occured inside cell
|
||||
//! - colmove({from,to}), colmove(): columns moved
|
||||
emits: ['cell', 'colmove'],
|
||||
|
||||
props: {
|
||||
...AList.props,
|
||||
//! Context object
|
||||
context: {type: Object, default: () => ({})},
|
||||
|
||||
//! Ordered list of columns, as objects with:
|
||||
//! - name: item attribute value
|
||||
//! - label: display label
|
||||
//! - help: help text
|
||||
//! - hidden: if true, field is hidden
|
||||
columns: Array,
|
||||
//! If True, columns are orderable
|
||||
columnsOrderable: Boolean,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
...super.data,
|
||||
// TODO: add observer
|
||||
columns_: [...this.columns],
|
||||
extraItem: new this.set.model(),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
columnNames() { return this.columns_.map(c => c.name) },
|
||||
columnLabels() { return this.columns_.reduce(
|
||||
(labels, c) => ({...labels, [c.name]: c.label}),
|
||||
{}
|
||||
)},
|
||||
rowSlots() {
|
||||
return Object.keys(this.$slots).filter(x => x.startsWith('row-'))
|
||||
.map(x => [x, x.slice(4)])
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
// TODO: use in tracklist
|
||||
sortColumns(names) {
|
||||
const ordered = names.map(n => this.columns_.find(c => c.name == n)).filter(c => !!c);
|
||||
const remaining = this.columns_.filter(c => names.indexOf(c.name) == -1)
|
||||
this.columns_ = [...ordered, ...remaining]
|
||||
this.$emit('colmove')
|
||||
},
|
||||
|
||||
/**
|
||||
* Move column using provided event object (as `{from, to}`)
|
||||
*/
|
||||
moveColumn(event) {
|
||||
const {from, to} = event
|
||||
const value = this.columns_[from]
|
||||
this.columns_.splice(from, 1)
|
||||
this.columns_.splice(to, 0, value)
|
||||
this.$emit('colmove', event)
|
||||
},
|
||||
|
||||
/**
|
||||
* React on 'cell' event, re-emitting it with additional values:
|
||||
* - `set`: data set
|
||||
* - `row`: row index
|
||||
*
|
||||
* @param {Number} row: row index
|
||||
* @param {} data: cell's event data
|
||||
*/
|
||||
onCellEvent(row, event) {
|
||||
if(event.name == 'focus')
|
||||
this.focus(event.data, event.cell)
|
||||
this.$emit('cell', {
|
||||
...event, row,
|
||||
set: this.set
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return row component at provided index
|
||||
*/
|
||||
getRow(row) {
|
||||
const els = this.$el.querySelectorAll('tr')
|
||||
for(var el of els)
|
||||
if(el.__row && row == Number(el.dataset.row))
|
||||
return el.__row
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus on a cell
|
||||
*/
|
||||
focus(row, col, from=null) {
|
||||
if(from)
|
||||
row += from.row
|
||||
row = this.getRow(row)
|
||||
row && row.focus(col, from)
|
||||
},
|
||||
},
|
||||
}
|
||||
Component.props.itemTag.default = 'tr'
|
||||
Component.props.listTag.default = 'tbody'
|
||||
|
||||
export default Component
|
||||
</script>
|
|
@ -1,167 +0,0 @@
|
|||
<template>
|
||||
<a-modal ref="modal" :title="title">
|
||||
<template #bar>
|
||||
<button type="button" class="button small mr-3" v-if="panel == LIST"
|
||||
@click="showPanel(UPLOAD)">
|
||||
<span class="icon">
|
||||
<i class="fa fa-upload"></i>
|
||||
</span>
|
||||
<span>{{ labels.upload }}</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="button small mr-3" v-else
|
||||
@click="showPanel(LIST)">
|
||||
<span class="icon">
|
||||
<i class="fa fa-list"></i>
|
||||
</span>
|
||||
<span>{{ labels.list }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<template #default>
|
||||
<a-file-upload ref="upload" v-if="panel == UPLOAD"
|
||||
:url="uploadUrl"
|
||||
:label="uploadLabel" :field-name="uploadFieldName"
|
||||
@load="uploadDone">
|
||||
<template #form="data">
|
||||
<slot name="upload-form" v-bind="data"></slot>
|
||||
</template>
|
||||
<template #preview="data">
|
||||
<slot name="upload-preview" v-bind="data"></slot>
|
||||
</template>
|
||||
</a-file-upload>
|
||||
<div class="a-select-file" v-else>
|
||||
<div ref="list"
|
||||
:class="['a-select-file-list', listClass]">
|
||||
<!-- tiles -->
|
||||
<div v-if="prevUrl">
|
||||
<a href="#" @click="load(prevUrl)">
|
||||
{{ labels.show_previous }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<template v-for="item in items" v-bind:key="item.id">
|
||||
<div :class="['file-preview', this.item && item.id == this.item.id && 'active']" @click="select(item)">
|
||||
<slot :item="item" :load="load" :lastUrl="lastUrl"></slot>
|
||||
<a-action-button v-if="deleteUrl"
|
||||
class="has-text-danger small float-right"
|
||||
icon="fa fa-trash"
|
||||
:confirm="labels.confirm_delete"
|
||||
method="DELETE"
|
||||
:url="deleteUrl.replace('123', item.id)"
|
||||
@done="load(lastUrl)">
|
||||
</a-action-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="nextUrl">
|
||||
<a href="#" @click="load(nextUrl)">
|
||||
{{ labels.show_next }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<slot name="footer" :item="item">
|
||||
<span class="mr-3" v-if="item">{{ item.name }}</span>
|
||||
</slot>
|
||||
<button type="button" v-if="panel == LIST" class="button align-right"
|
||||
@click="selected">
|
||||
{{ labels.select_file }}
|
||||
</button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import AModal from "./AModal"
|
||||
import AActionButton from "./AActionButton"
|
||||
import AFileUpload from "./AFileUpload"
|
||||
|
||||
export default {
|
||||
emit: ["select"],
|
||||
|
||||
components: {AActionButton, AFileUpload, AModal},
|
||||
|
||||
props: {
|
||||
title: { type: String },
|
||||
labels: Object,
|
||||
listClass: {type: String, default: ""},
|
||||
|
||||
// List url
|
||||
listUrl: { type: String },
|
||||
|
||||
// URL to delete an item, where "123" is replaced by
|
||||
// the item id.
|
||||
deleteUrl: {type: String },
|
||||
|
||||
uploadUrl: { type: String },
|
||||
uploadFieldName: { type: String, default: "file" },
|
||||
uploadLabel: { type: String, default: "Upload a file" },
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
LIST: 0,
|
||||
UPLOAD: 1,
|
||||
|
||||
panel: 0,
|
||||
item: null,
|
||||
items: [],
|
||||
nextUrl: "",
|
||||
prevUrl: "",
|
||||
lastUrl: "",
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.modal.open()
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$refs.modal.close()
|
||||
},
|
||||
|
||||
showPanel(panel) {
|
||||
this.panel = panel
|
||||
},
|
||||
|
||||
load(url) {
|
||||
return fetch(url || this.listUrl).then(
|
||||
response => response.ok ? response.json() : Promise.reject(response)
|
||||
).then(data => {
|
||||
this.lastUrl = url
|
||||
this.nextUrl = data.next
|
||||
this.prevUrl = data.previous
|
||||
this.items = data.results
|
||||
this.showPanel(this.LIST)
|
||||
|
||||
this.$forceUpdate()
|
||||
this.$refs.list.scroll(0, 0)
|
||||
return this.items
|
||||
})
|
||||
},
|
||||
|
||||
//! Select an item
|
||||
select(item) {
|
||||
this.item = item;
|
||||
},
|
||||
|
||||
//! User click on select button (confirm selection)
|
||||
selected() {
|
||||
this.$emit("select", this.item)
|
||||
this.close()
|
||||
},
|
||||
|
||||
uploadDone(reload=false) {
|
||||
reload && this.load().then(items => {
|
||||
this.item = items[0]
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.load()
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,63 +0,0 @@
|
|||
<template>
|
||||
<div :class="['a-sound-item m-0 button-group', playing && 'playing' || '']">
|
||||
<slot name="title" :player="player" :item="item" :loaded="loaded">
|
||||
<span :class="['label is-flex-grow-1 align-left', playing && 'blink' || '']" @click.stop="$emit('togglePlay')">
|
||||
{{ name || item.name }}
|
||||
</span>
|
||||
</slot>
|
||||
<slot name="after-title" :player="player" :item="item" :loaded="loaded">
|
||||
</slot>
|
||||
<div class="button-group actions">
|
||||
<a class="button action" v-if="hasAction('page')"
|
||||
:href="item.data.page_url">
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button action"
|
||||
v-if="hasAction('download') && item.data.is_downloadable"
|
||||
:href="item.data.url" target="_blank">
|
||||
<span class="icon is-small">
|
||||
<span class="fa fa-download"></span>
|
||||
</span>
|
||||
</a>
|
||||
<button :class="['button action', pinned ? 'selected' : 'not-selected']"
|
||||
v-if="hasAction('pin') && player && player.sets.pin != $parent.set" @click.stop="player.togglePlaylist('pin', item)">
|
||||
<span class="icon is-small">
|
||||
<span class="fa fa-star"></span>
|
||||
</span>
|
||||
</button>
|
||||
<slot name="actions" :player="player" :item="item" :loaded="loaded"></slot>
|
||||
</div>
|
||||
<slot name="extra-right" :player="player" :item="item" :loaded="loaded"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Model from '../model';
|
||||
import Sound from '../sound';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {type: Object, default: () => {}},
|
||||
name: String,
|
||||
player: Object,
|
||||
page_url: String,
|
||||
actions: {type:Array, default: () => []},
|
||||
index: {type:Number, default: null},
|
||||
},
|
||||
|
||||
computed: {
|
||||
item() { return this.data instanceof Model ? this.data : new Sound(this.data || {}); },
|
||||
loaded() { return this.player && this.player.isLoaded(this.item) },
|
||||
playing() { return this.player && this.player.isPlaying(this.item) },
|
||||
paused() { return this.player && this.player.paused && this.loaded },
|
||||
pinned() { return this.player && this.player.sets.pin.find(this.item) },
|
||||
},
|
||||
|
||||
methods: {
|
||||
hasAction(action) {
|
||||
return this.actions && this.actions.indexOf(action) != -1;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,83 +0,0 @@
|
|||
<template>
|
||||
<div class="a-playlist-editor">
|
||||
<a-select-file ref="select-file"
|
||||
:title="labels && labels.add_sound"
|
||||
:labels="labels"
|
||||
:list-url="soundListUrl"
|
||||
:deleteUrl="soundDeleteUrl"
|
||||
:uploadUrl="soundUploadUrl"
|
||||
:uploadLabel="labels.select_file"
|
||||
@select="selected"
|
||||
>
|
||||
<template #upload-preview="{upload}">
|
||||
<slot name="upload-preview" :upload="upload"></slot>
|
||||
</template>
|
||||
<template #upload-form>
|
||||
<slot name="upload-form"></slot>
|
||||
</template>
|
||||
<template #default="{item}">
|
||||
<audio controls :src="item.url"></audio>
|
||||
<label class="label small flex-grow-1">{{ item.name }}</label>
|
||||
</template>
|
||||
</a-select-file>
|
||||
|
||||
<a-form-set ref="formset" :form-data="formData" :labels="labels"
|
||||
:initials="initData.items"
|
||||
order-by="position"
|
||||
:action-add="actionAdd">
|
||||
<template v-for="[name,slot] of rowsSlots" :key="slot"
|
||||
v-slot:[slot]="data">
|
||||
<slot v-if="name != 'row-tail'" :name="name" v-bind="data"/>
|
||||
</template>
|
||||
|
||||
<template #row-sound="{item,inputName}">
|
||||
<label>{{ item.data.name }}</label><br>
|
||||
<audio controls :src="item.data.url"/>
|
||||
<input type="hidden" :name="inputName" :value="item.data.sound"/>
|
||||
</template>
|
||||
</a-form-set>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AFormSet from './AFormSet'
|
||||
import ASelectFile from "./ASelectFile"
|
||||
|
||||
export default {
|
||||
components: {AFormSet, ASelectFile},
|
||||
|
||||
props: {
|
||||
formData: Object,
|
||||
labels: Object,
|
||||
// initial datas
|
||||
initData: Object,
|
||||
|
||||
soundListUrl: String,
|
||||
soundUploadUrl: String,
|
||||
soundDeleteUrl: String,
|
||||
},
|
||||
|
||||
computed: {
|
||||
rowsSlots() {
|
||||
return Object.keys(this.$slots)
|
||||
.filter(x => x.startsWith('row-') || x.startsWith('rows-') || x.startsWith('control-'))
|
||||
.map(x => [x, x.startsWith('rows-') ? x.slice(5) : x])
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
actionAdd() {
|
||||
this.$refs['select-file'].open()
|
||||
},
|
||||
|
||||
selected(item) {
|
||||
const data = {
|
||||
"sound": item.id,
|
||||
"name": item.name,
|
||||
"url": item.url,
|
||||
"broadcast": item.broadcast,
|
||||
}
|
||||
this.$refs.formset.set.push(data)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,41 +0,0 @@
|
|||
<template>
|
||||
<form ref="form">
|
||||
<slot :counts="counts"></slot>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const splitReg = new RegExp(',\\s*|\\s+', 'g');
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
counts: {},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const items = this.$el.querySelectorAll('input[name="data"]:checked')
|
||||
const counts = {};
|
||||
|
||||
for(var item of items)
|
||||
if(item.value)
|
||||
for(var tag of item.value.split(splitReg))
|
||||
if(tag.trim())
|
||||
counts[tag.trim()] = (counts[tag.trim()] || 0) + 1;
|
||||
this.counts = counts;
|
||||
},
|
||||
|
||||
onclick() {
|
||||
// TODO: row click => check checkbox
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
console.log(this.counts)
|
||||
this.$refs.form.addEventListener('change', () => this.update())
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,57 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot :streamer="streamer" :streamers="streamers" :Sound="Sound"
|
||||
:sources="sources" :fetchStreamers="fetchStreamers"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Sound from '../sound';
|
||||
import {setEcoInterval} from '../utils';
|
||||
|
||||
import Streamer from '../streamer';
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
apiUrl: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// current streamer
|
||||
streamer: null,
|
||||
// all streamers
|
||||
streamers: [],
|
||||
// fetch interval id
|
||||
fetchInterval: null,
|
||||
Sound: Sound,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
sources() {
|
||||
var sources = this.streamer ? this.streamer.sources : [];
|
||||
return sources.filter(s => s.data)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchStreamers() {
|
||||
Streamer.fetch(this.apiUrl, {many:true}).then(streamers => {
|
||||
this.streamers = streamers
|
||||
this.streamer = streamers ? streamers[0] : null
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchStreamers();
|
||||
this.fetchInterval = setEcoInterval(() => this.streamer && this.streamer.fetch(), 5000)
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
if(this.fetchInterval !== null)
|
||||
clearInterval(this.fetchInterval)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,80 +0,0 @@
|
|||
<template>
|
||||
<button :title="ariaLabel"
|
||||
type="button"
|
||||
:aria-label="ariaLabel || label" :aria-description="ariaDescription"
|
||||
@click="toggle" :class="buttonClass">
|
||||
<slot name="default" :active="active">
|
||||
<span class="icon">
|
||||
<i :class="icon"></i>
|
||||
</span>
|
||||
<label v-if="label">{{ label }}</label>
|
||||
</slot>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
initialActive: {type: Boolean, default: null},
|
||||
el: {type: String, default: ""},
|
||||
label: {type: String, default: ""},
|
||||
icon: {type: String, default: "fa fa-bars"},
|
||||
ariaLabel: {type: String, default: ""},
|
||||
ariaDescription: {type: String, default: ""},
|
||||
activeClass: {type: String, default:"active"},
|
||||
/// switch toggle of all items of this group.
|
||||
group: {type: String, default: ""},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
active: this.initialActive,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
groupClass() {
|
||||
return this.group && "a-switch-" + this.group || ''
|
||||
},
|
||||
|
||||
buttonClass() {
|
||||
return [
|
||||
this.active && 'active' || '',
|
||||
this.groupClass
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
this.set(!this.active)
|
||||
},
|
||||
|
||||
set(active) {
|
||||
if(this.el) {
|
||||
const el = document.querySelector(this.el)
|
||||
if(active)
|
||||
el.classList.add(this.activeClass)
|
||||
else
|
||||
el.classList.remove(this.activeClass)
|
||||
}
|
||||
this.active = active
|
||||
if(active)
|
||||
this.resetGroup()
|
||||
},
|
||||
|
||||
resetGroup() {
|
||||
if(!this.groupClass)
|
||||
return
|
||||
const els = document.querySelectorAll("." + this.groupClass)
|
||||
for(var el of els)
|
||||
if(el != this.$el)
|
||||
el.__vnode.ctx.ctx.set(false)
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if(this.initialActive !== null)
|
||||
this.set(this.initialActive)
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,288 +0,0 @@
|
|||
<template>
|
||||
<div class="a-tracklist-editor">
|
||||
<div class="flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<slot name="title" />
|
||||
</div>
|
||||
<div class="flex-row align-right">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<button type="button" :class="['button','p-2', page == Page.Text ? 'is-primary' : 'is-light']"
|
||||
@click="page = Page.Text">
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</span>
|
||||
<span>{{ labels.text }}</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button type="button" :class="['button','p-2', page == Page.List ? 'is-primary' : 'is-light']"
|
||||
@click="page = Page.List">
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-list"></i>
|
||||
</span>
|
||||
<span>{{ labels.list }}</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="control ml-3">
|
||||
<button type="button" class="button is-info square"
|
||||
:title="labels.settings"
|
||||
@click="$refs.settings.open()">
|
||||
<span class="icon is-small">
|
||||
<i class="fa fa-cog"></i>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section v-show="page == Page.Text" class="panel">
|
||||
<textarea ref="textarea" class="is-fullwidth is-size-6" rows="20"
|
||||
@change="updateList"
|
||||
/>
|
||||
|
||||
</section>
|
||||
<section v-show="page == Page.List" class="panel">
|
||||
<a-form-set ref="formset"
|
||||
:form-data="formData" :initials="initData.items"
|
||||
:columnsOrderable="true" :labels="labels"
|
||||
order-by="position"
|
||||
@load="updateInput" @colmove="onColumnMove" @move="updateInput"
|
||||
@cell="onCellEvent">
|
||||
<template v-for="[name,slot] of rowsSlots" :key="slot"
|
||||
v-slot:[slot]="data">
|
||||
<slot v-if="name != 'row-tail'" :name="name" v-bind="data"/>
|
||||
</template>
|
||||
</a-form-set>
|
||||
</section>
|
||||
|
||||
<a-modal ref="settings" :title="labels.settings">
|
||||
<template #default>
|
||||
<div class="field">
|
||||
<label class="label" style="vertical-align: middle">
|
||||
{{ labels.columns }}
|
||||
</label>
|
||||
<table class="table is-bordered"
|
||||
style="vertical-align: middle">
|
||||
<tr v-if="$refs.formset">
|
||||
<a-row :columns="$refs.formset.rows.columnNames"
|
||||
:item="$refs.formset.rows.columnLabels"
|
||||
@move="$refs.formset.rows.moveColumn"
|
||||
>
|
||||
<template v-slot:cell-after="{cell}">
|
||||
<td style="cursor:pointer;" v-if="cell.col < $refs.formset.rows.columns_.length-1">
|
||||
<span class="icon" @click="$refs.formset.rows.moveColumn({from: cell.col, to: cell.col+1})"
|
||||
><i class="fa fa-left-right"/>
|
||||
</span>
|
||||
</td>
|
||||
</template>
|
||||
</a-row>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex-row">
|
||||
<div class="field is-inline-block is-vcentered flex-grow-1">
|
||||
<label class="label is-inline mr-2"
|
||||
style="vertical-align: middle">
|
||||
Séparateur</label>
|
||||
<div class="control is-inline-block"
|
||||
style="vertical-align: middle;">
|
||||
<input type="text" ref="sep" class="input is-inline is-text-centered is-small"
|
||||
style="max-width: 5em;"
|
||||
v-model="separator" @change="updateList()"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex-row align-right">
|
||||
<a-action-button icon="fa fa-floppy-disk"
|
||||
v-if="settingsChanged"
|
||||
class="button control p-2 mr-3 is-secondary" run-class="blink"
|
||||
:url="settingsUrl" method="POST"
|
||||
:data="settings"
|
||||
:aria-label="labels.save_settings"
|
||||
@done="settingsSaved()">
|
||||
{{ labels.save_settings }}
|
||||
</a-action-button>
|
||||
<button class="button" type="button" @click="$refs.settings.close()">
|
||||
Fermer
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {dropRightWhile, cloneDeep, isEqual} from 'lodash'
|
||||
|
||||
import AActionButton from './AActionButton'
|
||||
import AFormSet from './AFormSet'
|
||||
import ARow from './ARow'
|
||||
import AModal from "./AModal"
|
||||
|
||||
/// Page display
|
||||
export const Page = {
|
||||
Text: 0, List: 1, Settings: 2,
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { AActionButton, AFormSet, ARow, AModal },
|
||||
props: {
|
||||
formData: Object,
|
||||
labels: Object,
|
||||
|
||||
///! initial data as: {items: [], fields: {column_name: label, settings: {}}
|
||||
initData: Object,
|
||||
dataPrefix: String,
|
||||
settingsUrl: String,
|
||||
defaultColumns: {
|
||||
type: Array,
|
||||
default: () => ['artist', 'title', 'tags', 'album', 'year', 'timestamp']},
|
||||
},
|
||||
|
||||
data() {
|
||||
const settings = {
|
||||
// tracklist_editor_columns: this.columns,
|
||||
tracklist_editor_sep: ' -- ',
|
||||
}
|
||||
return {
|
||||
Page: Page,
|
||||
page: Page.Text,
|
||||
extraData: {},
|
||||
settings,
|
||||
savedSettings: cloneDeep(settings),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
rows() { return this.$refs.formset && this.$refs.formset.rows },
|
||||
columns() { return this.rows && this.rows.columns_ || [] },
|
||||
|
||||
settingsChanged() {
|
||||
var k = Object.keys(this.savedSettings)
|
||||
.findIndex(k => !isEqual(this.settings[k], this.savedSettings[k]))
|
||||
return k != -1
|
||||
},
|
||||
|
||||
separator: {
|
||||
set(value) {
|
||||
this.settings.tracklist_editor_sep = value
|
||||
if(this.page == Page.List)
|
||||
this.updateInput()
|
||||
},
|
||||
get() { return this.settings.tracklist_editor_sep }
|
||||
},
|
||||
|
||||
rowsSlots() {
|
||||
return Object.keys(this.$slots)
|
||||
.filter(x => x.startsWith('row-') || x.startsWith('rows-') || x.startsWith('control-'))
|
||||
.map(x => [x, x.startsWith('rows-') ? x.slice(5) : x])
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onCellEvent(event) {
|
||||
switch(event.name) {
|
||||
case 'change': this.updateInput();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onColumnMove() {
|
||||
this.settings.tracklist_editor_columns = this.$refs.formset.rows.columnNames
|
||||
if(this.page == this.Page.List)
|
||||
this.updateInput()
|
||||
else
|
||||
this.updateList()
|
||||
},
|
||||
|
||||
updateList() {
|
||||
const items = this.toList(this.$refs.textarea.value)
|
||||
this.$refs.formset.set.reset(items)
|
||||
},
|
||||
|
||||
updateInput() {
|
||||
const input = this.toText(this.$refs.formset.items)
|
||||
this.$refs.textarea.value = input
|
||||
},
|
||||
|
||||
/**
|
||||
* From input and separator, return list of items.
|
||||
*/
|
||||
toList(input) {
|
||||
const columns = this.$refs.formset.rows.columns_
|
||||
var lines = input.split('\n')
|
||||
var items = []
|
||||
|
||||
for(let line of lines) {
|
||||
line = line.trimLeft()
|
||||
if(!line)
|
||||
continue
|
||||
|
||||
var lineBits = line.split(this.separator)
|
||||
var item = {}
|
||||
for(var col in columns) {
|
||||
if(col >= lineBits.length)
|
||||
break
|
||||
const column = columns[col]
|
||||
item[column.name] = lineBits[col].trim()
|
||||
}
|
||||
item && items.push(item)
|
||||
}
|
||||
return items
|
||||
},
|
||||
|
||||
/**
|
||||
* From items and separator return a string
|
||||
*/
|
||||
toText(items) {
|
||||
const columns = this.$refs.formset.rows.columns_
|
||||
const sep = ` ${this.separator.trim()} `
|
||||
const lines = []
|
||||
for(let item of items) {
|
||||
if(!item)
|
||||
continue
|
||||
var line = []
|
||||
for(var col of columns)
|
||||
line.push(item.data[col.name] || '')
|
||||
line = dropRightWhile(line, x => !x || !('' + x).trim())
|
||||
line = line.join(sep).trimRight()
|
||||
lines.push(line)
|
||||
}
|
||||
return lines.join('\n')
|
||||
},
|
||||
|
||||
|
||||
_data_key(key) {
|
||||
key = key.slice(this.dataPrefix.length)
|
||||
try {
|
||||
var [index, attr] = key.split('-', 1)
|
||||
return [Number(index), attr]
|
||||
}
|
||||
catch(err) {
|
||||
return [null, key]
|
||||
}
|
||||
},
|
||||
|
||||
//! Update saved settings from this.settings
|
||||
settingsSaved(settings=null) {
|
||||
if(settings !== null)
|
||||
this.settings = settings
|
||||
if(this.$refs.settings)
|
||||
this.$refs.settings.close()
|
||||
this.savedSettings = cloneDeep(this.settings)
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const settings = this.initData && this.initData.settings
|
||||
if(settings) {
|
||||
this.settingsSaved(settings)
|
||||
this.rows.sortColumns(settings.tracklist_editor_columns)
|
||||
}
|
||||
this.page = this.initData.items.length ? Page.List : Page.Text
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,24 +0,0 @@
|
|||
import AFileUpload from "./AFileUpload.vue"
|
||||
import ASelectFile from "./ASelectFile.vue"
|
||||
import AStatistics from './AStatistics.vue'
|
||||
import AStreamer from './AStreamer.vue'
|
||||
|
||||
import AFormSet from './AFormSet.vue'
|
||||
import ATrackListEditor from './ATrackListEditor.vue'
|
||||
import ASoundListEditor from './ASoundListEditor.vue'
|
||||
import AEditor from './AEditor.vue'
|
||||
|
||||
import AManyToManyEdit from "./AManyToManyEdit.vue"
|
||||
|
||||
import base from "./index.js"
|
||||
|
||||
|
||||
export const admin = {
|
||||
...base,
|
||||
AManyToManyEdit,
|
||||
AFileUpload, ASelectFile, AEditor,
|
||||
AFormSet, ATrackListEditor, ASoundListEditor,
|
||||
AStatistics, AStreamer,
|
||||
}
|
||||
|
||||
export default admin
|
|
@ -1,26 +0,0 @@
|
|||
import AAutocomplete from './AAutocomplete.vue'
|
||||
import AModal from "./AModal.vue"
|
||||
import AActionButton from './AActionButton.vue'
|
||||
import ADropdown from "./ADropdown.vue"
|
||||
import ACarousel from './ACarousel.vue'
|
||||
import AEpisode from './AEpisode.vue'
|
||||
import AList from './AList.vue'
|
||||
import APage from './APage.vue'
|
||||
import APlayer from './APlayer.vue'
|
||||
import APlaylist from './APlaylist.vue'
|
||||
import AProgress from './AProgress.vue'
|
||||
import ASoundItem from './ASoundItem.vue'
|
||||
import ASwitch from './ASwitch.vue'
|
||||
|
||||
|
||||
/**
|
||||
* Core components
|
||||
*/
|
||||
export const base = {
|
||||
AActionButton, AAutocomplete, AModal,
|
||||
ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
|
||||
AProgress, ASoundItem, ASwitch,
|
||||
|
||||
}
|
||||
|
||||
export default base
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
* This module includes code available for both the public website and
|
||||
* administration interface)
|
||||
*/
|
||||
|
||||
import 'vue'
|
||||
|
||||
//-- aircox
|
||||
import App, {PlayerApp} from './app'
|
||||
import VueLoader from './vueLoader'
|
||||
import Sound from './sound'
|
||||
import {Set} from './model'
|
||||
|
||||
import './styles/common.scss'
|
||||
|
||||
|
||||
window.aircox = {
|
||||
// main application
|
||||
loader: null,
|
||||
get app() { return this.loader.app },
|
||||
|
||||
// player application
|
||||
playerLoader: null,
|
||||
get playerApp() { return this.playerLoader && this.playerLoader.app },
|
||||
get player() { return this.playerLoader.vm && this.playerLoader.vm.$refs.player },
|
||||
|
||||
Set, Sound,
|
||||
|
||||
|
||||
/**
|
||||
* Initialize main application and player.
|
||||
*/
|
||||
init(props=null, {hotReload=false, el=null,
|
||||
config=null, playerConfig=null,
|
||||
initApp=true, initPlayer=true,
|
||||
loader=null, playerLoader=null}={})
|
||||
{
|
||||
if(initPlayer) {
|
||||
playerConfig = playerConfig || PlayerApp
|
||||
playerLoader = playerLoader || new VueLoader(playerConfig)
|
||||
playerLoader.enable(false)
|
||||
this.playerLoader = playerLoader
|
||||
|
||||
document.addEventListener("keyup", e => this.onKeyPress(e), false)
|
||||
}
|
||||
|
||||
if(initApp) {
|
||||
config = config || window.App || App
|
||||
config.el = el || config.el
|
||||
loader = loader || new VueLoader({el, props, ...config})
|
||||
loader.enable(hotReload)
|
||||
this.loader = loader
|
||||
}
|
||||
},
|
||||
|
||||
onKeyPress(/*event*/) {
|
||||
/*
|
||||
if(event.key == " ") {
|
||||
this.player.togglePlay()
|
||||
event.stopPropagation()
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter navbar dropdown menu items
|
||||
*/
|
||||
filter_menu(event) {
|
||||
var filter = new RegExp(event.target.value, 'gi');
|
||||
var container = event.target.closest('.navbar-dropdown');
|
||||
|
||||
if(event.target.value)
|
||||
for(let item of container.querySelectorAll('a.navbar-item'))
|
||||
item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;
|
||||
else
|
||||
for(let item of container.querySelectorAll('a.navbar-item'))
|
||||
item.style.display = null;
|
||||
},
|
||||
|
||||
pickDate(url, date) {
|
||||
url = `${url}?date=${date.id}`
|
||||
this.loader.pageLoad.load(url)
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
import {setEcoInterval} from './utils';
|
||||
import Model from './model';
|
||||
|
||||
export default class Live {
|
||||
constructor({url,timeout=10,src=""}={}) {
|
||||
this.url = url;
|
||||
this.timeout = timeout;
|
||||
this.src = src;
|
||||
|
||||
this.interval = null
|
||||
this.promise = null
|
||||
this.items = []
|
||||
this.current = null
|
||||
}
|
||||
|
||||
//-- data refreshing
|
||||
drop() {
|
||||
this.promise = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data from server.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Function} options.then: call this method on fetch, `this` passed as argument.
|
||||
* @return {Promise} Promise resolving to fetched items.
|
||||
*/
|
||||
fetch({then=null}={}) {
|
||||
const promise = fetch(this.url).then(response =>
|
||||
response.ok ? response.json()
|
||||
: Promise.reject(response)
|
||||
).then(data => {
|
||||
data = data.results
|
||||
data.forEach(item => {
|
||||
if(item.start) item.start = new Date(item.start)
|
||||
if(item.end) item.end = new Date(item.end)
|
||||
})
|
||||
this.items = data
|
||||
|
||||
const now = new Date()
|
||||
let item = data.find(it => it.start && (it.start <= now < it.end)) ||
|
||||
data.length ? data[0] : null;
|
||||
if(item) {
|
||||
item.src = this.src
|
||||
this.current = new Model(item)
|
||||
}
|
||||
else
|
||||
this.current = null
|
||||
if(then)
|
||||
then(this)
|
||||
return this.items
|
||||
})
|
||||
|
||||
this.promise = promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
_refresh(options={}) {
|
||||
const promise = this.fetch(options);
|
||||
promise.then(() => {
|
||||
if(promise != this.promise)
|
||||
return [];
|
||||
})
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh live info every `this.timeout`.
|
||||
* @param {Object} options: arguments passed to `this.fetch`.
|
||||
*/
|
||||
refresh(options={}) {
|
||||
if(this.interval !== null)
|
||||
return
|
||||
|
||||
this._refresh(options)
|
||||
this.interval = setEcoInterval(() => this._refresh(options), this.timeout*1000)
|
||||
return this.interval
|
||||
}
|
||||
|
||||
stopRefresh() {
|
||||
this.interval !== null && clearInterval(this.interval)
|
||||
}
|
||||
}
|
|
@ -1,371 +0,0 @@
|
|||
|
||||
/**
|
||||
* Return cookie with provided key
|
||||
*/
|
||||
function getCookie(key) {
|
||||
if(document.cookie && document.cookie !== '') {
|
||||
const cookie = document.cookie.split(';')
|
||||
.find(c => c.trim().startsWith(key + '='))
|
||||
return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF token provided by Django
|
||||
*/
|
||||
var csrfToken = null;
|
||||
|
||||
/**
|
||||
* Get CSRF token
|
||||
*/
|
||||
export function getCsrf() {
|
||||
if(csrfToken === null)
|
||||
csrfToken = getCookie('csrftoken')
|
||||
return csrfToken;
|
||||
}
|
||||
|
||||
|
||||
// TODO: prevent duplicate simple fetch
|
||||
/**
|
||||
* Provide interface used to fetch and manipulate objects.
|
||||
*/
|
||||
export default class Model {
|
||||
/**
|
||||
* Instanciate model with provided data and options.
|
||||
* By default `url` is taken from `data.url_`.
|
||||
*/
|
||||
constructor(data={}, {url=null, ...options}={}) {
|
||||
this.url = url || data.url_;
|
||||
this.options = options;
|
||||
this.commit(data);
|
||||
}
|
||||
|
||||
get created() { return !this.id }
|
||||
get errors() { return this.data && this.data.__errors__ }
|
||||
|
||||
/**
|
||||
* Get instance id from its data
|
||||
*/
|
||||
static getId(data) {
|
||||
return 'id' in data ? data.id : data.pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fetch options
|
||||
*/
|
||||
static getOptions(options) {
|
||||
return {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-CSRFToken': getCsrf(),
|
||||
},
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return model instances for the provided list of model data.
|
||||
* @param {Array} items: array of data
|
||||
* @param {Object} options: options passed down to all model instances
|
||||
*/
|
||||
static fromList(items, options={}) {
|
||||
return items ? items.map(d => new this(d, options)) : []
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch item from server
|
||||
*/
|
||||
static fetch(url, {many=false, ...options}={}, args={}) {
|
||||
options = this.getOptions(options)
|
||||
const request = fetch(url, options).then(response => response.json());
|
||||
if(many)
|
||||
return request.then(data => {
|
||||
if(!(data instanceof Array))
|
||||
data = data.results
|
||||
return this.fromList(data, args)
|
||||
})
|
||||
else
|
||||
return request.then(data => new this(data, {url: url, ...args}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data from server.
|
||||
*/
|
||||
fetch(options) {
|
||||
options = this.constructor.getOptions(options)
|
||||
return fetch(this.url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => this.commit(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call API action on object.
|
||||
*/
|
||||
action(path, options, commit=false) {
|
||||
options = this.constructor.getOptions(options)
|
||||
const promise = fetch(this.url + path, options);
|
||||
return commit ? promise.then(data => data.json())
|
||||
.then(data => { this.commit(data); this.data })
|
||||
: promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set instance's data with provided data. Return None
|
||||
*/
|
||||
commit(data) {
|
||||
this.data = data;
|
||||
this.id = this.constructor.getId(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update model data, without reset previous value.
|
||||
* Item is marked as updated.
|
||||
*/
|
||||
update(data) {
|
||||
this.data = {...this.data, ...data}
|
||||
this.id = this.constructor.getId(this.data)
|
||||
this.updated = true
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.deleted = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Save instance into localStorage.
|
||||
*/
|
||||
store(key) {
|
||||
window.localStorage.setItem(key, JSON.stringify(this.data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load model instance from localStorage.
|
||||
*/
|
||||
static storeLoad(key) {
|
||||
let item = window.localStorage.getItem(key);
|
||||
return item === null ? item : new this(JSON.parse(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if model instance has no data
|
||||
*/
|
||||
get isEmpty() {
|
||||
return !this.data || Object.keys(this.data).findIndex(k => !!this.data[k] && this.data[k] !== 0) == -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Return error for a specific attribute name if any
|
||||
*/
|
||||
error(attr=null) {
|
||||
return attr === null ? this.errors : this.errors && this.errors[attr]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* List of models
|
||||
*/
|
||||
export class Set {
|
||||
constructor(model, {items=[],url=null,args={},unique=null,max=null,storeKey=null}={}) {
|
||||
this.items = [];
|
||||
this.model = model;
|
||||
this.url = url;
|
||||
this.unique = unique;
|
||||
this.max = max;
|
||||
this.storeKey = storeKey;
|
||||
|
||||
for(var item of items)
|
||||
this.push(item, {args: args, save: false});
|
||||
}
|
||||
|
||||
//! Return total items count
|
||||
get length() { return this.items.length }
|
||||
|
||||
//! Return a list of items marked as deleted
|
||||
get deletedItems() {
|
||||
return this.items.filter(i => i.deleted)
|
||||
}
|
||||
|
||||
//! Return a list of created items
|
||||
get createdItems() {
|
||||
return this.items.filter(i => !i.deleted && !i.id)
|
||||
}
|
||||
|
||||
//! Return a list of updated items
|
||||
get updatedItems() {
|
||||
return this.items.filter(i => i.updated)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch multiple items from server
|
||||
*/
|
||||
static fetch(model, url, options=null, args=null) {
|
||||
options = model.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => (data instanceof Array ? data : data.results)
|
||||
.map(d => new model(d, {url: url, ...args})))
|
||||
}
|
||||
|
||||
fetch({url=null, reset=false, ...options}={}, args=null) {
|
||||
url = url || this.url
|
||||
options = this.model.getOptions(options)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data =>
|
||||
(data instanceof Array ? data : data.results)
|
||||
.map(d => new this.model(d, {url: url, ...args}))
|
||||
)
|
||||
.then(data => {
|
||||
if(reset)
|
||||
this.items = data
|
||||
else
|
||||
// TODO: remove duplicate
|
||||
this.items = [...this.items, ...data]
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit changes to server.
|
||||
* py-ref: `views.mixin.ListCommitMixin`
|
||||
*/
|
||||
commit(url, {getData=null, fields=null, ...options}={}) {
|
||||
if(!getData && fields)
|
||||
getData = (i) => fields.reduce((r, f) => {
|
||||
r[f] = i.data[f]
|
||||
return r
|
||||
}, {})
|
||||
const createdItems = this.createdItems
|
||||
const body = {
|
||||
delete: this.deletedItems.map(i => i.id),
|
||||
update: this.updatedItems.map(getData),
|
||||
create: createdItems.map(getData),
|
||||
}
|
||||
if(!body.delete && !body.update && !body.create)
|
||||
return
|
||||
|
||||
getData = getData || ((i) => i.data);
|
||||
options = this.model.getOptions(options)
|
||||
options.method = "POST"
|
||||
options.body = JSON.stringify(body)
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const {created, updated, deleted} = data
|
||||
if(createdItems)
|
||||
this.items = this.items.filter(i => createdItems.indexOf(i) == -1)
|
||||
if(deleted)
|
||||
this.items = this.items.filter(i => deleted.indexOf(i.id) == -1)
|
||||
|
||||
this.extend(created)
|
||||
this.extend(updated)
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Load list from localStorage
|
||||
*/
|
||||
static storeLoad(model, key, args={}) {
|
||||
let items = window.localStorage.getItem(key);
|
||||
return new this(model, {...args, storeKey: key, items: items ? JSON.parse(items) : []});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store list into localStorage
|
||||
*/
|
||||
store() {
|
||||
this.storeKey && window.localStorage.setItem(this.storeKey, JSON.stringify(
|
||||
this.items.map(i => i.data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save item
|
||||
*/
|
||||
save() {
|
||||
this.storeKey && this.store();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item at index
|
||||
*/
|
||||
get(index) { return this.items[index] }
|
||||
|
||||
/**
|
||||
* Find an item by id or using a predicate function
|
||||
*/
|
||||
find(pred) {
|
||||
return pred instanceof Function ? this.items.find(pred)
|
||||
: this.items.find(x => x.id == pred.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find item index by id or using a predicate function
|
||||
*/
|
||||
findIndex(pred) {
|
||||
return pred instanceof Function ? this.items.findIndex(pred)
|
||||
: this.items.findIndex(x => x.id == pred.id);
|
||||
}
|
||||
|
||||
extend(items, options) {
|
||||
items.forEach(i => this.push(i, options))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to set, return index.
|
||||
* If item already exists, replace it.
|
||||
*/
|
||||
push(item, {args={},save=true}={}) {
|
||||
item = item instanceof this.model ? item : new this.model(item, args);
|
||||
let index = -1
|
||||
if(this.unique && item.id) {
|
||||
index = this.findIndex(item);
|
||||
if(index > -1)
|
||||
this.items[index] = item
|
||||
}
|
||||
if(index == -1) {
|
||||
if(this.max && this.items.length >= this.max)
|
||||
this.items.splice(0,this.items.length-this.max)
|
||||
this.items.push(item)
|
||||
index = this.items.length-1
|
||||
}
|
||||
save && this.save()
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from set by index
|
||||
*/
|
||||
remove(index, {save=true}={}) {
|
||||
this.items.splice(index,1);
|
||||
save && this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear items, assign new ones
|
||||
*/
|
||||
reset(items=[]) {
|
||||
// TODO: check reactivity
|
||||
this.items = []
|
||||
for(var item of items)
|
||||
this.push(item)
|
||||
}
|
||||
|
||||
move(from, to) {
|
||||
if(from >= this.length || to > this.length)
|
||||
throw "source or target index is not in range"
|
||||
|
||||
const value = this.items[from]
|
||||
this.items.splice(from, 1)
|
||||
this.items.splice(to, 0, value)
|
||||
}
|
||||
}
|
||||
|
||||
Set[Symbol.iterator] = function () {
|
||||
return this.items[Symbol.iterator]();
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
|
||||
/**
|
||||
* Load page without leaving current one (hot-reload).
|
||||
*/
|
||||
export default class PageLoad {
|
||||
constructor(el, {loadingClass="loading", append=false}={}) {
|
||||
this.el = el
|
||||
this.append = append
|
||||
this.loadingClass = loadingClass
|
||||
}
|
||||
|
||||
get target() {
|
||||
if(!this._target)
|
||||
this._target = document.querySelector(this.el)
|
||||
return this._target
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._target = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable hot reload: catch page change in order to fetch them and
|
||||
* load page without actually leaving current one.
|
||||
*/
|
||||
enable(target=null) {
|
||||
if(this._pageChanged)
|
||||
throw "Already enabled, please disable me"
|
||||
|
||||
if(!target)
|
||||
target = this.target || document.body
|
||||
this.historySave(document.location, true)
|
||||
|
||||
this._pageChanged = event => this.pageChanged(event)
|
||||
this._statePopped = event => this.statePopped(event)
|
||||
|
||||
target.addEventListener('click', this._pageChanged, true)
|
||||
target.addEventListener('submit', this._pageChanged, true)
|
||||
window.addEventListener('popstate', this._statePopped, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable hot reload, remove listeners.
|
||||
*/
|
||||
disable() {
|
||||
this.target.removeEventListener('click', this._pageChanged, true)
|
||||
this.target.removeEventListener('submit', this._pageChanged, true)
|
||||
window.removeEventListener('popstate', this._statePopped, true)
|
||||
|
||||
this._pageChanged = null
|
||||
this._statePopped = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch url, return promise, similar to standard Fetch API.
|
||||
* Default implementation just forward argument to it.
|
||||
*/
|
||||
fetch(url, options) {
|
||||
return fetch(url, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch app from remote and mount application.
|
||||
*/
|
||||
load(url, {mount=true, scroll=[0,0], ...options}={}) {
|
||||
if(this.loadingClass)
|
||||
this.target.classList.add(this.loadingClass)
|
||||
|
||||
if(this.onLoad)
|
||||
this.onLoad({url, el: this.el, options})
|
||||
if(scroll)
|
||||
window.scroll(...scroll)
|
||||
return this.fetch(url, options).then(response => response.text())
|
||||
.then(content => {
|
||||
if(this.loadingClass)
|
||||
this.target.classList.remove(this.loadingClass)
|
||||
|
||||
var doc = new DOMParser().parseFromString(content, 'text/html')
|
||||
var dom = doc.querySelectorAll(this.el)
|
||||
var result = {url,
|
||||
content: dom || [document.createTextNode(content)],
|
||||
title: doc.title,
|
||||
append: this.append}
|
||||
mount && this.mount(result)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the page on provided target element
|
||||
*/
|
||||
mount({content, title=null, ...options}={}) {
|
||||
if(this.onPreMount)
|
||||
this.onPreMount({target: this.target, content, items, title})
|
||||
var items = null;
|
||||
if(content)
|
||||
items = this.mountContent(content, options)
|
||||
if(title)
|
||||
document.title = title
|
||||
if(this.onMount)
|
||||
this.onMount({target: this.target, content, items, title})
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount page content
|
||||
*/
|
||||
mountContent(content, {append=false}={}) {
|
||||
if(typeof content == "string") {
|
||||
this.target.innerHTML = append ? this.target.innerHTML + content
|
||||
: content;
|
||||
// TODO
|
||||
return []
|
||||
}
|
||||
|
||||
if(!append)
|
||||
this.target.innerHTML = ""
|
||||
|
||||
var fragment = document.createDocumentFragment()
|
||||
var items = []
|
||||
for(var node of content)
|
||||
while(node.firstChild) {
|
||||
items.push(node.firstChild)
|
||||
fragment.appendChild(node.firstChild)
|
||||
}
|
||||
this.target.append(fragment)
|
||||
return items
|
||||
}
|
||||
|
||||
/// Save application state into browser history
|
||||
historySave(url,replace=false) {
|
||||
const state = { content: this.target.innerHTML,
|
||||
title: document.title, }
|
||||
if(replace)
|
||||
history.replaceState(state, '', url)
|
||||
else
|
||||
history.pushState(state, '', url)
|
||||
}
|
||||
|
||||
dispatchPageLoaded(url) {
|
||||
var evt = new CustomEvent("pageLoaded", {detail: url})
|
||||
document.dispatchEvent(evt)
|
||||
}
|
||||
|
||||
// --- events
|
||||
pageChanged(event) {
|
||||
let submit = event.type == 'submit';
|
||||
let target = submit || event.target.tagName == 'A'
|
||||
? event.target : event.target.closest('a');
|
||||
if(!target || target.hasAttribute('target') || (target.dataset && target.dataset.forceReload))
|
||||
return;
|
||||
|
||||
let url = submit ? target.getAttribute('action') || ''
|
||||
: target.getAttribute('href');
|
||||
let domain = window.location.protocol + '//' + window.location.hostname
|
||||
let stay = (url === '' || url.startsWith('/') || url.startsWith('?') ||
|
||||
url.startsWith(domain)) && url.indexOf('wp-admin') == -1
|
||||
if(url===null || !stay) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = {};
|
||||
if(submit) {
|
||||
let formData = new FormData(event.target);
|
||||
if(target.method == 'get')
|
||||
url += '?' + (new URLSearchParams(formData)).toString();
|
||||
else
|
||||
options = {...options, method: target.method, body: formData}
|
||||
}
|
||||
this.load(url, options).then(() => this.dispatchPageLoaded(url)).then(() => this.historySave(url))
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
statePopped(event) {
|
||||
const state = event.state
|
||||
if(state && state.content)
|
||||
this.mount({ content: state.content, title: state.title });
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import "./styles/public.scss"
|
||||
import './index.js'
|
||||
import App from './app.js'
|
||||
|
||||
window.App = App
|
|
@ -1,12 +0,0 @@
|
|||
import Model from './model';
|
||||
|
||||
|
||||
export default class Sound extends Model {
|
||||
constructor({sound={}, ...data}={}, options={}) {
|
||||
// flatten EpisodeSound and sound data
|
||||
super({...sound, ...data}, options)
|
||||
}
|
||||
|
||||
get name() { return this.data.name }
|
||||
get src() { return this.data.url }
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
import Model from './model';
|
||||
import {setEcoInterval} from './utils';
|
||||
|
||||
|
||||
export class Streamer extends Model {
|
||||
get playlists() { return this.data ? this.data.playlists : []; }
|
||||
get queues() { return this.data ? this.data.queues : []; }
|
||||
get sources() { return [...this.queues, ...this.playlists]; }
|
||||
get source() { return this.sources.find(o => o.id == this.data.source) }
|
||||
|
||||
commit(data) {
|
||||
if(!this.data)
|
||||
this.data = { id: data.id, playlists: [], queues: [] }
|
||||
|
||||
data.playlists = Playlist.fromList(data.playlists, {streamer: this});
|
||||
data.queues = Queue.fromList(data.queues, {streamer: this});
|
||||
super.commit(data)
|
||||
}
|
||||
}
|
||||
|
||||
export default Streamer;
|
||||
|
||||
export class Request extends Model {
|
||||
static getId(data) { return data.rid; }
|
||||
}
|
||||
|
||||
export class Source extends Model {
|
||||
constructor(data, {streamer=null, ...options}={}) {
|
||||
super(data, options);
|
||||
this.streamer = streamer;
|
||||
setEcoInterval(() => this.tick(), 1000)
|
||||
}
|
||||
|
||||
get isQueue() { return false; }
|
||||
get isPlaylist() { return false; }
|
||||
get isPlaying() { return this.data.status == 'playing' }
|
||||
get isPaused() { return this.data.status == 'paused' }
|
||||
|
||||
get remainingString() {
|
||||
if(!this.remaining)
|
||||
return '00:00';
|
||||
|
||||
const seconds = Math.floor(this.remaining % 60);
|
||||
const minutes = Math.floor(this.remaining / 60);
|
||||
return String(minutes).padStart(2, '0') + ':' +
|
||||
String(seconds).padStart(2, '0');
|
||||
}
|
||||
|
||||
sync() { return this.action('sync/', {method: 'POST'}, true); }
|
||||
skip() { return this.action('skip/', {method: 'POST'}, true); }
|
||||
restart() { return this.action('restart/', {method: 'POST'}, true); }
|
||||
|
||||
seek(count) {
|
||||
return this.action('seek/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({count: count})
|
||||
}, true)
|
||||
}
|
||||
|
||||
tick() {
|
||||
if(!this.data.remaining || !this.isPlaying)
|
||||
return;
|
||||
const delta = (Date.now() - this.commitDate) / 1000;
|
||||
this.remaining = this.data.remaining - delta
|
||||
}
|
||||
|
||||
commit(data) {
|
||||
if(data.air_time)
|
||||
data.air_time = new Date(data.air_time);
|
||||
|
||||
this.commitDate = Date.now()
|
||||
super.commit(data)
|
||||
this.remaining = data.remaining
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Playlist extends Source {
|
||||
get isPlaylist() { return true; }
|
||||
}
|
||||
|
||||
|
||||
export class Queue extends Source {
|
||||
get isQueue() { return true; }
|
||||
get queue() { return this.data && this.data.queue; }
|
||||
|
||||
commit(data) {
|
||||
data.queue = Request.fromList(data.queue);
|
||||
super.commit(data)
|
||||
}
|
||||
|
||||
push(soundId) {
|
||||
return this.action('push/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({'sound_id': parseInt(soundId)})
|
||||
}, true);
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import AdminApp from '../admin';
|
||||
import Model from '../model';
|
||||
import Sound from '../sound';
|
||||
import {setEcoInterval} from '../utils';
|
||||
|
||||
import {Streamer, Queue} from './controllers';
|
||||
|
||||
|
||||
export default {
|
||||
...AdminApp,
|
||||
|
||||
props: {
|
||||
...(AdminApp.props || {}),
|
||||
apiUrl: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// current streamer
|
||||
streamer: null,
|
||||
// all streamers
|
||||
streamers: [],
|
||||
// fetch interval id
|
||||
fetchInterval: null,
|
||||
Sound: Sound,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...(AdminApp.computed || {}),
|
||||
|
||||
sources() {
|
||||
var sources = this.streamer ? this.streamer.sources : [];
|
||||
return sources.filter(s => s.data)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...(AdminApp.methods || {}),
|
||||
|
||||
fetchStreamers() {
|
||||
Streamer.fetch(this.apiUrl, {many:true}).then(streamers => {
|
||||
this.streamers = streamers
|
||||
this.streamer = streamers ? streamers[0] : null
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchStreamers();
|
||||
this.fetchInterval = setEcoInterval(() => this.streamer && this.streamer.fetch(), 5000)
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if(this.fetchInterval !== null)
|
||||
clearInterval(this.fetchInterval)
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot :streamer="streamer" :streamers="streamers" :Sound="Sound"
|
||||
:sources="sources" :fetchStreamers="fetchStreamers"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AdminApp from '../admin';
|
||||
import Sound from '../sound';
|
||||
import {setEcoInterval} from '../utils';
|
||||
|
||||
import {Streamer} from './controllers';
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
apiUrl: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// current streamer
|
||||
streamer: null,
|
||||
// all streamers
|
||||
streamers: [],
|
||||
// fetch interval id
|
||||
fetchInterval: null,
|
||||
Sound: Sound,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
sources() {
|
||||
var sources = this.streamer ? this.streamer.sources : [];
|
||||
return sources.filter(s => s.data)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchStreamers() {
|
||||
Streamer.fetch(this.apiUrl, {many:true}).then(streamers => {
|
||||
this.streamers = streamers
|
||||
this.streamer = streamers ? streamers[0] : null
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchStreamers();
|
||||
this.fetchInterval = setEcoInterval(() => this.streamer && this.streamer.fetch(), 5000)
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if(this.fetchInterval !== null)
|
||||
clearInterval(this.fetchInterval)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1 +0,0 @@
|
|||
../../../../assets/src/styles/*
|
|
@ -1,101 +0,0 @@
|
|||
@use "./vars";
|
||||
@use "./components";
|
||||
|
||||
@import "bulma/sass/utilities/_all.sass";
|
||||
@import "bulma/sass/elements/button";
|
||||
@import "bulma/sass/components/navbar";
|
||||
|
||||
|
||||
// enforce button usage inside custom application
|
||||
#player, .ax {
|
||||
@include components.button;
|
||||
}
|
||||
|
||||
|
||||
.admin {
|
||||
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
a.navbar-item.is-active {
|
||||
border-bottom: 1px grey solid;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
& + .container {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.navbar-split {
|
||||
margin: 0.2em 0em;
|
||||
margin-right: 1em;
|
||||
padding-right: 1em;
|
||||
border-right: 1px vars.$grey-light solid;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
&.toolbar {
|
||||
margin: 1em 0em;
|
||||
background-color: transparent;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.title {
|
||||
padding-right: 2em;
|
||||
margin-right: 1em;
|
||||
border-right: 1px vars.$grey-light solid;
|
||||
|
||||
font-size: vars.$text-size;
|
||||
font-weight: vars.$weight-light;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
max-height: 40rem;
|
||||
overflow-y: auto;
|
||||
|
||||
input {
|
||||
z-index: 10000;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.navbar .navbar-brand {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand img {
|
||||
margin: 0em 0.4em;
|
||||
margin-top: 0.3em;
|
||||
max-height: 3em;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.results > #result_list {
|
||||
width: 100%;
|
||||
margin: 1em 0em;
|
||||
}
|
||||
|
||||
|
||||
ul.menu-list li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.submit-row a.deletelink {
|
||||
height: 35px;
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
@use "./vars" as v;
|
||||
@import "./vendor";
|
||||
@import "./helpers";
|
||||
|
||||
//-- helpers/modifiers
|
||||
//-- forms
|
||||
input.half-field:not(:active):not(:hover) {
|
||||
border: none;
|
||||
background-color: rgba(0,0,0,0);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
//-- general
|
||||
:root {
|
||||
--body-bg: #fff;
|
||||
--text-color: black;
|
||||
--text-color-light: #555;
|
||||
--break-color: rgb(225, 225, 225, 0.8);
|
||||
|
||||
--main-color: #EFCA08;
|
||||
--main-color-light: #F4da51;
|
||||
--main-color-dark: #F49F0A;
|
||||
--secondary-color: #00A6A6;
|
||||
--secondary-color-light: #4cc0c0;
|
||||
--secondary-color-dark: #007ba8;
|
||||
|
||||
--disabled-color: #aaa;
|
||||
--disabled-bg: #eee;
|
||||
--link-fg: #00A6A6;
|
||||
--link-hv-fg: var(--text-color);
|
||||
|
||||
--nav-primary-height: 3rem;
|
||||
--nav-secondary-height: 2.5rem;
|
||||
--nav-fg: var(--text-color);
|
||||
--nav-bg: var(--main-color);
|
||||
--nav-secondary-bg: var(--main-color-light);
|
||||
--nav-hv-fg: var(--button-hv-fg);
|
||||
--nav-hv-bg: var(--button-hv-bg);
|
||||
--nav-active-fg: var(--button-active-fg);
|
||||
--nav-active-bg: var(--button-active-bg);
|
||||
--nav-fs: 1rem;
|
||||
--nav-2-fs: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--body-bg);
|
||||
}
|
||||
|
||||
|
||||
@mixin mobile-small {
|
||||
.grid { @include grid-1; }
|
||||
}
|
||||
|
||||
|
||||
body.mobile {
|
||||
@include mobile-small;
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-smaller) {
|
||||
@include mobile-small;
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-normal) {
|
||||
html { font-size: 16px !important; }
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-wider) {
|
||||
html { font-size: 20px !important; }
|
||||
}
|
||||
|
||||
@media screen and (min-width: v.$screen-wider) {
|
||||
html { font-size: 20px !important; }
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
|
||||
font-family: var(--heading-font-family);
|
||||
}
|
||||
|
||||
|
||||
.container:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-cover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.modal .dropdown-menu {
|
||||
z-index: 50,
|
||||
}
|
|
@ -1,782 +0,0 @@
|
|||
@use "vars" as v;
|
||||
|
||||
:root {
|
||||
--title-1-sz: 1.4rem;
|
||||
--title-2-sz: 1.3rem;
|
||||
--title-3-sz: 1.1rem;
|
||||
--title-4-sz: 1.0rem;
|
||||
--subtitle-1-sz: 1.6rem;
|
||||
--subtitle-2-sz: 1.4rem;
|
||||
--subtitle-3-sz: 1.2rem;
|
||||
|
||||
--heading-font-family: default;
|
||||
--heading-bg: var(--main-color);
|
||||
--heading-fg: var(--text-color);
|
||||
--heading-hg-fg: var(--text-color);
|
||||
--heading-hg-bg: var(--secondary-color);
|
||||
--heading-link-hv-fg: var(--link-fg);
|
||||
|
||||
--cover-w: 10rem;
|
||||
--cover-h: 10rem;
|
||||
--cover-small-w: 10rem;
|
||||
--cover-small-h: 10rem;
|
||||
--cover-tiny-w: 10rem;
|
||||
--cover-tiny-h: 10rem;
|
||||
|
||||
--card-w: var(--cover-w);
|
||||
--preview-bg: var(--body-bg);
|
||||
--preview-title-sz: var(--title-4-sz);
|
||||
--preview-subtitle-sz: var(--title-4-sz);
|
||||
--preview-wide-content-sz: #{v.$text-size-2};
|
||||
--preview-heading-bg-color: var(--main-color);
|
||||
--header-height: var(--cover-h);
|
||||
|
||||
--a-carousel-p: #{v.$text-size-medium};
|
||||
--a-carousel-ml: calc(#{v.$mp-4} - 0.5rem);
|
||||
--a-carousel-gap: #{v.$mp-4};
|
||||
--a-carousel-nav-x: -#{v.$mp-3e};
|
||||
--a-carousel-bg: none; // var(--secondary-color-light);
|
||||
|
||||
--a-progress-bg: transparent;
|
||||
--a-progress-bar-bg: var(--secondary-color);
|
||||
--a-progress-bar-color: var(--text-color);
|
||||
--a-progress-bar-pd: #{v.$mp-2};
|
||||
|
||||
--a-playlist-header-bg: var(--secondary-color);
|
||||
--a-playlist-header-fg: var(--text-color);
|
||||
--a-playlist-title-sz: #{v.$text-size};
|
||||
--a-playlist-title-pd: #{v.$mp-3};
|
||||
--a-playlist-item-border: 1px var(--secondary-color) solid;
|
||||
|
||||
--a-sound-bg: var(--main-color);
|
||||
--a-sound-hv-bg: var(--main-color);
|
||||
--a-sound-hv-fg: var(--secondary-color);
|
||||
--a-sound-playing-fg: var(--secondary-color-dark);
|
||||
--a-sound-text-sz: #{v.$text-size};
|
||||
|
||||
--a-player-url-fg: var(--text-color);
|
||||
--a-player-panel-bg: var(--main-color);
|
||||
--a-player-bar-height: var(--nav-primary-height);
|
||||
--a-player-bar-bg: var(--main-color);
|
||||
--a-player-bar-title-alone-sz: #{v.$text-size-medium};
|
||||
--a-player-bar-button-fg: var(--button-fg);
|
||||
--a-player-bar-button-fg: var(--button-bg);
|
||||
--a-player-bar-button-hv-fg: var(--button-hv-fg);
|
||||
--a-player-bar-button-hv-bg: var(--button-hv-bg);
|
||||
|
||||
--button-fg: var(--text-color);
|
||||
--button-bg: var(--main-color);
|
||||
--button-sec-bg: var(--main-color-light);
|
||||
--button-hv-fg: var(--text-color);
|
||||
--button-hv-bg: var(--secondary-color-light);
|
||||
--button-active-fg: var(--text-color);
|
||||
--button-active-bg: var(--secondary-color);
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: v.$screen-wide) {
|
||||
:root {
|
||||
--cover-w: 10rem;
|
||||
--cover-h: 10rem;
|
||||
--cover-small-w: 6rem;
|
||||
--cover-small-h: 6rem;
|
||||
--cover-tiny-w: 4rem;
|
||||
--cover-tiny-h: 4rem;
|
||||
|
||||
--section-content-sz: 1rem;
|
||||
|
||||
// --preview-title-sz: #{v.$text-size};
|
||||
// --preview-subtitle-sz: #{v.$text-size-smaller};
|
||||
// --preview-wide-content-sz: #{v.$text-size};
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-wide) {
|
||||
:root {
|
||||
--cover-w: 8rem;
|
||||
--cover-h: 8rem;
|
||||
--cover-small-w: 4rem;
|
||||
--cover-small-h: 4rem;
|
||||
--cover-tiny-w: 2rem;
|
||||
--cover-tiny-h: 2rem;
|
||||
|
||||
--section-content-sz: 1rem;
|
||||
|
||||
// --preview-title-sz: #{v.$text-size};
|
||||
// --preview-subtitle-sz: #{v.$text-size-smaller};
|
||||
// --preview-wide-content-sz: #{v.$text-size};
|
||||
}
|
||||
}
|
||||
|
||||
// ---- headings
|
||||
|
||||
.no-reset h1 { font-size: var(--title-1-sz); }
|
||||
.no-reset h2 { font-size: var(--title-2-sz); }
|
||||
.no-reset h3 { font-size: var(--title-3-sz); }
|
||||
.no-reset h3 { font-size: var(--title-3-sz); }
|
||||
.no-reset h4 { font-size: var(--title-4-sz); }
|
||||
.no-reset h5 { font-size: var(--title-5-sz); }
|
||||
|
||||
.title, .header.preview .title {
|
||||
&.is-1 { font-size: var(--title-1-sz); }
|
||||
&.is-2 { font-size: var(--title-2-sz); }
|
||||
&.is-3 { font-size: var(--title-3-sz); }
|
||||
}
|
||||
|
||||
.subtitle, .header.preview .subtitle {
|
||||
color: var(--text-color-light);
|
||||
|
||||
&.is-1 { font-size: var(--subtitle-1-sz); }
|
||||
&.is-2 { font-size: var(--subtitle-2-sz); }
|
||||
&.is-3 { font-size: var(--subtitle-3-sz); }
|
||||
}
|
||||
|
||||
.title + .subtitle {
|
||||
padding-top: 0em !important;
|
||||
}
|
||||
|
||||
.headings a, a.heading, a.subtitle {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: inline-block;
|
||||
|
||||
&:not(:empty) {
|
||||
// border-bottom: 1px var(--heading-bg) solid;
|
||||
// color: var(--heading-fg);
|
||||
//padding: v.$mp-2;
|
||||
margin-top: 0em !important;
|
||||
vertical-align: top;
|
||||
|
||||
&.highlight, &.active,
|
||||
.preview.active &,
|
||||
{
|
||||
// border-color: var(--heading-hg-bg);
|
||||
color: var(--heading-hg-fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- bulma overrides
|
||||
.modal-card {
|
||||
max-width: v.$screen-wide;
|
||||
}
|
||||
.modal-card {
|
||||
max-height: calc(100% - 10rem);
|
||||
}
|
||||
|
||||
// ---- button
|
||||
@mixin button {
|
||||
.button, a.button, button.button {
|
||||
font-size: v.$text-size;
|
||||
display: inline-block;
|
||||
padding: v.$mp-2e;
|
||||
border: none;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
color: var(--button-fg);
|
||||
background-color: var(--button-bg);
|
||||
|
||||
&.square { min-width: 2.5em; }
|
||||
&.secondary { background-color: var(--button-sec-bg); }
|
||||
|
||||
.label, label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
&:not(:only-child) {
|
||||
&:first-child { margin: 0 v.$mp-3e 0 v.$mp-1e; }
|
||||
&:last-child { margin: 0 v.$mp-3e 0 v.$mp-1e; }
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--button-hv-fg);
|
||||
background-color: var(--button-hv-bg);
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
&.active:not(:hover) {
|
||||
color: var(--button-active-fg);
|
||||
background-color: var(--button-active-bg);
|
||||
}
|
||||
|
||||
&:not([disabled]), &:not(.disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&[disabled], &.disabled {
|
||||
background-color: var(--text-color-light);
|
||||
color: var(--secondary-color);
|
||||
border-color: var(--secondary-color-light);
|
||||
}
|
||||
|
||||
.dropdown-trigger {
|
||||
border-radius: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.button-group, .nav {
|
||||
.button {
|
||||
border-radius: 0px;
|
||||
background-color: transparent;
|
||||
border-top: 0px;
|
||||
border-bottom: 0px;
|
||||
height: 100%;
|
||||
|
||||
&:not(:first-child) { border-left: 0px; }
|
||||
&:last-child { border-right: 0px; }
|
||||
}
|
||||
}
|
||||
|
||||
.button-group + .button-group {
|
||||
border-left: 1px solid var(--text-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- preview
|
||||
.preview {
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-color: var(--preview-bg) !important;
|
||||
|
||||
&.preview-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
&.columns, .headings.columns {
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
.column { padding: 0em; }
|
||||
}
|
||||
|
||||
.title, .title:not(:last-child) {
|
||||
// second is bulma reset
|
||||
font-weight: v.$weight-bold;
|
||||
font-size: var(--preview-title-sz);
|
||||
margin-bottom: unset;
|
||||
}
|
||||
.subtitle {
|
||||
font-weight: v.$weight-bolder;
|
||||
font-size: var(--preview-subtitle-sz);
|
||||
margin-bottom: unset;
|
||||
}
|
||||
//.content, .actions {
|
||||
// font-size: v.$text-size-bigger;
|
||||
//}
|
||||
|
||||
.headings {
|
||||
background-size: cover;
|
||||
|
||||
> * { margin: 0em; }
|
||||
.column { padding: 0em; }
|
||||
|
||||
a { color: var(--text-color); }
|
||||
a:hover { color: var(--heading-link-hv-fg) !important; }
|
||||
}
|
||||
|
||||
&.tiny {
|
||||
.title { font-size: calc(var(--preview-title-sz) * 0.8); }
|
||||
.subtitle { font-size: calc(var(--preview-subtitle-sz) * 0.8); }
|
||||
.content {
|
||||
font-size: v.$text-size;
|
||||
max-height: 3rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.preview-cover {
|
||||
background: var(--preview-bg);
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
height: var(--cover-h);
|
||||
max-width: calc( var(--cover-w) * 1.5 );
|
||||
min-width: var(--cover-w);
|
||||
overflow: hidden;
|
||||
border: 1px #c4c4c4 solid;
|
||||
|
||||
img {
|
||||
height: var(--cover-h);
|
||||
max-width: calc( var(--cover-w) * 1.5 );
|
||||
min-width: var(--cover-w);
|
||||
}
|
||||
img.hide { visibility: hidden; }
|
||||
|
||||
|
||||
&.small, .preview.small & {
|
||||
min-width: unset;
|
||||
height: var(--cover-small-h);
|
||||
width: var(--cover-small-w) !important;
|
||||
min-width: var(--cover-small-w);
|
||||
}
|
||||
|
||||
&.tiny, .preview.tiny & {
|
||||
min-width: unset;
|
||||
height: var(--cover-tiny-h);
|
||||
width: var(--cover-tiny-w) !important;
|
||||
min-width: var(--cover-tiny-w);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
// width: 100%;
|
||||
|
||||
/*&:not(.no-cover) {
|
||||
min-height: var(--header-height);
|
||||
}*/
|
||||
|
||||
&.no-cover {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.headings {
|
||||
padding-top: v.$mp-6;
|
||||
}
|
||||
|
||||
.headings, > .container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> .container, {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- list
|
||||
.list-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
// padding: v.$mp-3;
|
||||
|
||||
.headings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0em;
|
||||
margin-bottom: v.$mp-2 !important;
|
||||
|
||||
.heading {
|
||||
// background-color: var(--preview-heading-bg-color);
|
||||
padding: 0rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.title { flex-grow: 1; }
|
||||
.subtitle {
|
||||
font-size: var(--preview-title-sz);
|
||||
// background-color: var(--preview-heading-bg-color);
|
||||
text-align: right;
|
||||
|
||||
&:not(:empty) { min-width: 9rem; }
|
||||
}
|
||||
|
||||
.media-content {
|
||||
height: 100%;
|
||||
margin-bottom: unset;
|
||||
|
||||
.list-item:not(.no-cover) & {
|
||||
min-height: var(--cover-small-h);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: right;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:not(.wide) .media {
|
||||
padding: v.$mp-3;
|
||||
// border-radius: v.$mp-2;
|
||||
border: 1px solid var(--break-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-very-small) {
|
||||
.list-item .headings {
|
||||
flex-direction: column;
|
||||
|
||||
.heading {
|
||||
display: inline;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: unset !important;
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- wide
|
||||
.list-item.wide {
|
||||
& .preview-cover {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
& .content {
|
||||
font-size: var(--preview-wide-content-sz);
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- card
|
||||
.preview-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--card-w);
|
||||
padding: 0rem !important;
|
||||
margin-bottom: auto;
|
||||
|
||||
background-color: var(--preview-bg) !important;
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
figure {
|
||||
// box-shadow: 0em 0em 1.2em rgba(0, 0, 0, 0.4) !important;
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--heading-link-hv-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.headings {
|
||||
margin-top: v.$mp-2;
|
||||
|
||||
.heading {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: v.$text-size-2;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
figure {
|
||||
// box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
|
||||
height: var(--cover-h);
|
||||
width: var(--cover-w);
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
padding: v.$mp-2;
|
||||
bottom: 0rem;
|
||||
right: 0rem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- ---- Carousel
|
||||
.a-carousel {
|
||||
.a-carousel-viewport {
|
||||
box-shadow: inset 0em 0em 20rem var(--a-carousel-bg);
|
||||
// background-color: var(--a-carousel-bg);
|
||||
padding: 0rem;
|
||||
padding-top: var(--a-carousel-p);
|
||||
margin-top: calc( 0rem - var(--a-carousel-p) );
|
||||
}
|
||||
}
|
||||
|
||||
.a-carousel-container {
|
||||
width: 100%;
|
||||
gap: var(--a-carousel-gap);
|
||||
transition: margin-left 1s;
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.a-carousel-bullets-container {
|
||||
// due to a-carousel margin-left
|
||||
padding-left: var(--a-carousel-ml);
|
||||
|
||||
.bullet {
|
||||
margin: v.$mp-1;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { color: var(--link-fg); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- ---- progress bar
|
||||
.a-progress {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--a-progress-bg);
|
||||
}
|
||||
|
||||
.a-progress-bar-container {
|
||||
flex-grow: 1;
|
||||
margin: 0em;
|
||||
}
|
||||
|
||||
> time, .a-progress-bar {
|
||||
height: 100%;
|
||||
padding: var(--a-progress-bar-pd);
|
||||
}
|
||||
|
||||
.a-progress-bar {
|
||||
background-color: var(--a-progress-bar-bg);
|
||||
color: var(--a-progress-bar-color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- ---- player
|
||||
// ---- playlist
|
||||
.playlist, .a-playlist {
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.title, .button {
|
||||
background-color: var(--a-playlist-header-bg);
|
||||
color: var(--a-playlist-header-fg);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--a-playlist-title-sz);
|
||||
margin: 0;
|
||||
padding: var(--a-playlist-title-pd);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
border-bottom: var(--a-playlist-item-border);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- sound item
|
||||
.a-sound-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
height: 3rem;
|
||||
background-color: var(--a-sound-bg);
|
||||
|
||||
&.playing .label {
|
||||
color: var(--a-sound-playing-fg) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--a-sound-hv-bg);
|
||||
|
||||
.label {
|
||||
color: var(--a-sound-hv-fg) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.label:hover::before, &.playing .label::before {
|
||||
content: "\f04b";
|
||||
font-family: "Font Awesome 6 Free";
|
||||
margin-right: v.$mp-3e;
|
||||
}
|
||||
&.playing .label:hover::before {
|
||||
content: '';
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.headings > * {
|
||||
}
|
||||
|
||||
.label {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
padding: 0em v.$mp-3;
|
||||
}
|
||||
|
||||
margin: 0em !important;
|
||||
padding: v.$mp-3e;
|
||||
font-size: var(--a-sound-text-sz);
|
||||
font-family: var(--heading-font-family);
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 3em;
|
||||
font-size: var(--a-sound-text-sz);
|
||||
|
||||
&:hover {
|
||||
color: var(--a-sound-hv-fg) !important;
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- player
|
||||
.player-container {
|
||||
z-index: 1000000;
|
||||
}
|
||||
|
||||
.a-player {
|
||||
box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
|
||||
|
||||
a { color: var(--a-player-url-fg); }
|
||||
.button {
|
||||
color: var(--text-black);
|
||||
&:hover { color: var(--button-fg); }
|
||||
}
|
||||
}
|
||||
|
||||
.a-player-panels {
|
||||
background: var(--a-player-panel-bg);
|
||||
height: 0%;
|
||||
transition: height 1s;
|
||||
}
|
||||
.a-player-panels.is-open {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.a-player-panel {
|
||||
padding-bottom: v.$mp-3;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
|
||||
.a-sound-item:not(:hover) {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.a-player-progress {
|
||||
height: 0.4em;
|
||||
overflow: hidden;
|
||||
|
||||
time { display: none; }
|
||||
|
||||
&:hover, .a-player-panels.is-open + & {
|
||||
background: var(--a-player-bar-bg);
|
||||
height: 2em;
|
||||
time { display: unset; }
|
||||
}
|
||||
}
|
||||
|
||||
.a-player-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
height: var(--a-player-bar-height);
|
||||
|
||||
border-top: 1px v.$grey-light solid;
|
||||
background: var(--a-player-bar-bg);
|
||||
|
||||
> * { height: 100%; }
|
||||
|
||||
.cover { height: 100%; }
|
||||
.title {
|
||||
font-size: v.$text-size;
|
||||
margin: 0em;
|
||||
|
||||
&:last-child {
|
||||
font-size: var(--a-player-bar-title-alone-sz);
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: v.$text-size-medium;
|
||||
height: 100%;
|
||||
padding: v.$mp-2 !important;
|
||||
min-width: calc(var(--a-player-bar-height) + v.$mp-2 * 2);
|
||||
border-radius: 0px;
|
||||
|
||||
&.open {
|
||||
background-color: var(--button-active-bg);
|
||||
color: var(--button-active-fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.a-player-bar-content {
|
||||
display: flex;
|
||||
flex-direction: vertical;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
padding: 0 v.$mp-3;
|
||||
border-right: 1px black solid;
|
||||
|
||||
.title {
|
||||
max-height: calc( var(--a-player-bar-height) - v.$mp-3 );
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// ---- playlist editor
|
||||
.a-tracklist-editor {
|
||||
.dropdown {
|
||||
display: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
/// ----------------
|
||||
.a-select-file {
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: v.$mp-3;
|
||||
}
|
||||
|
||||
.upload-preview {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.a-select-file-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: v.$mp-3;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
max-height: 10rem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
@use "./vars" as v;
|
||||
|
||||
// ---- text
|
||||
.text-light { font-weight: 400; color: var(--text-color-light); }
|
||||
|
||||
.bigger { font-size: v.$text-size-bigger !important; }
|
||||
.big { font-size: v.$text-size-big !important; }
|
||||
.smaller { font-size: v.$text-size-smaller !important; }
|
||||
.small { font-size: v.$text-size-small !important; }
|
||||
|
||||
// ---- layout
|
||||
.align-left {
|
||||
text-align: left;
|
||||
justify-content: left;
|
||||
|
||||
&.x { padding-left: 0px !important; }
|
||||
}
|
||||
.align-right {
|
||||
text-align: right;
|
||||
justify-content: right;
|
||||
|
||||
&.x { padding-right: 0px !important; }
|
||||
}
|
||||
.align-center {
|
||||
text-align: center !important;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.clear-left { clear: left !important }
|
||||
.clear-right { clear: right !important }
|
||||
.clear-both { clear: both !important }
|
||||
.clear-unset { clear: unset !important }
|
||||
|
||||
.d-inline { display: inline !important; }
|
||||
.d-block { display: block !important; }
|
||||
.d-inline-block { display: inline-block !important; }
|
||||
|
||||
.p-relative { position: relative !important }
|
||||
.p-absolute { position: absolute !important }
|
||||
.p-fixed { position: fixed !important }
|
||||
.p-sticky { position: sticky !important }
|
||||
.p-static { position: static !important }
|
||||
|
||||
.ws-nowrap { white-space: nowrap; }
|
||||
|
||||
|
||||
.height-1 { height: 1em; }
|
||||
.height-2 { height: 2em; }
|
||||
.height-3 { height: 3em; }
|
||||
.height-4 { height: 4em; }
|
||||
.height-5 { height: 5em; }
|
||||
.height-6 { height: 6em; }
|
||||
.height-7 { height: 7em; }
|
||||
.height-8 { height: 8em; }
|
||||
.height-9 { height: 9em; }
|
||||
.height-10 { height: 10em; }
|
||||
.height-15 { height: 15em; }
|
||||
.height-20 { height: 20em; }
|
||||
.height-25 { height: 25em; }
|
||||
|
||||
// ---- grid / flex
|
||||
|
||||
.gap-1 { gap: v.$mp-1 !important; }
|
||||
.gap-2 { gap: v.$mp-2 !important; }
|
||||
.gap-3 { gap: v.$mp-3 !important; }
|
||||
.gap-4 { gap: v.$mp-4 !important; }
|
||||
.gap-5 { gap: v.$mp-5 !important; }
|
||||
|
||||
|
||||
// ---- ---- grid
|
||||
@mixin grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-flow: dense;
|
||||
gap: v.$mp-4;
|
||||
}
|
||||
@mixin grid-1 { grid-template-columns: 1fr; }
|
||||
@mixin grid-2 { grid-template-columns: 1fr 1fr; }
|
||||
@mixin grid-3 { grid-template-columns: 1fr 1fr 1fr; }
|
||||
|
||||
.grid { @include grid; }
|
||||
.grid-1 { @include grid; @include grid-1; }
|
||||
.grid-2 { @include grid; @include grid-2; }
|
||||
.grid-3 { @include grid; @include grid-3; }
|
||||
|
||||
// ---- ---- flex
|
||||
.flex-row { display: flex; flex-direction: row }
|
||||
.flex-column { display: flex; flex-direction: column }
|
||||
.flex-grow-0 { flex-grow: 0 !important; }
|
||||
.flex-grow-1 { flex-grow: 1 !important; }
|
||||
.flex-grow-2 { flex-grow: 2 !important; }
|
||||
.flex-grow-3 { flex-grow: 3 !important; }
|
||||
.flex-grow-4 { flex-grow: 4 !important; }
|
||||
.flex-grow-5 { flex-grow: 5 !important; }
|
||||
.flex-grow-6 { flex-grow: 6 !important; }
|
||||
|
||||
.float-right { float: right }
|
||||
.float-left { float: left }
|
||||
|
||||
// ---- boxing
|
||||
.is-fullwidth { width: 100%; }
|
||||
.is-fullheight { height: 100%; }
|
||||
.is-fixed-bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
margin-bottom: 0px;
|
||||
border-radius: 0;
|
||||
}
|
||||
.no-border { border: 0px !important; }
|
||||
|
||||
.overflow-hidden { overflow: hidden }
|
||||
.overflow-hidden.is-fullwidth { max-width: 100%; }
|
||||
|
||||
.height-full { height: 100%; }
|
||||
|
||||
*[draggable="true"] {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
|
||||
// ---- animations
|
||||
@keyframes blink {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.4; }
|
||||
}
|
||||
|
||||
.blink { animation: 1s ease-in-out 3s infinite alternate blink; }
|
||||
.loading { animation: 1s ease-in-out 1s infinite alternate blink; }
|
||||
|
||||
|
||||
// -- colors
|
||||
.main-color { color: var(--main-color); }
|
||||
.secondary-color { color: var(--secondary-color); }
|
||||
|
||||
.bg-main { background-color: var(--main-color); }
|
||||
.bg-main-light { background-color: var(--main-color-light); }
|
||||
.bg-secondary { background-color: var(--secondary-color); }
|
||||
.bg-secondary-light { background-color: var(--secondary-color-light); }
|
||||
.bg-transparent { background-color: transparent; }
|
||||
|
||||
.border { border: 1px solid var(--text-color); }
|
||||
.border-main { border: 1px solid var(--main-color); }
|
||||
.border-secondary { border: 1px solid var(--secondary-color); }
|
||||
.border-bottom-main { border-bottom: 1px solid var(--main-color); }
|
||||
.border-bottom-secondary { border-bottom: 1px solid var(--secondary-color); }
|
||||
|
||||
.is-success {
|
||||
background-color: v.$green !important;
|
||||
border-color: v.$green-dark !important;
|
||||
}
|
||||
.is-danger {
|
||||
background-color: v.$red !important;
|
||||
border-color: v.$red-dark !important;
|
||||
}
|
||||
|
||||
|
||||
.box-shadow {
|
||||
&:hover {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.4);
|
||||
}
|
||||
}
|
|
@ -1,478 +0,0 @@
|
|||
@use "./vars" as v;
|
||||
@use "./components";
|
||||
|
||||
|
||||
// ---- main theme & layout
|
||||
|
||||
.page {
|
||||
padding-bottom: 5rem;
|
||||
|
||||
a {
|
||||
color: var(--link-fg);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--link-hv-fg);
|
||||
}
|
||||
}
|
||||
|
||||
section.container {
|
||||
margin-top: v.$mp-3;
|
||||
margin-bottom: v.$mp-4;
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: calc(v.$mp-4 / 2);
|
||||
// border-bottom: 2px var(--break-color) solid;
|
||||
}
|
||||
|
||||
> .title, h3.title {
|
||||
font-size: var(--title-2-sz);
|
||||
clear: both;
|
||||
margin: v.$mp-3 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*[data-oembed-url] {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- components
|
||||
.dropdown-item {
|
||||
font-size: unset !important
|
||||
}
|
||||
|
||||
.vc-weekday-1, .vc-weekday-7 {
|
||||
color: var(--secondary-color) !important;
|
||||
}
|
||||
|
||||
|
||||
.schedules {
|
||||
padding-top: 0;
|
||||
margin-bottom: calc(0rem - v.$mp-3) !important;
|
||||
}
|
||||
|
||||
.schedule {
|
||||
display: inline-block;
|
||||
margin: v.$mp-3;
|
||||
margin-left: 0rem;
|
||||
padding: v.$mp-2;
|
||||
text-color: var(--main-color);
|
||||
background-color: var(--main-color-light);
|
||||
|
||||
.heading {
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
.day {
|
||||
font-weight: v.$weight-bold;
|
||||
margin-right: v.$mp-3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -- buttons, forms
|
||||
@include components.button;
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: v.$mp-3;
|
||||
justify-content: right;
|
||||
|
||||
&.no-label label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button, .action, a {
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
padding: v.$mp-2;
|
||||
|
||||
.not-selected { opacity: 0.6; }
|
||||
.icon { margin: 0em !important; }
|
||||
label { margin-left: v.$mp-2; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.label, .textarea, .input, .select {
|
||||
font-size: v.$text-size;
|
||||
}
|
||||
|
||||
.field.is-horizontal {
|
||||
display: flex;
|
||||
flex-direction: horizontal;
|
||||
|
||||
.label { min-width: 7rem }
|
||||
.control {
|
||||
flex: 1;
|
||||
> * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: v.$screen-small) {
|
||||
comment.textarea {
|
||||
height: calc( v.$text-size * 7 ) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-item.active, .table tr.is-selected {
|
||||
color: var(--secondary-color);
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
|
||||
// -- headings
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
&.is-3 { margin-top: v.$mp-3; }
|
||||
}
|
||||
|
||||
|
||||
// ---- main navigation
|
||||
.navs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
background-color: var(--nav-bg);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.burger {
|
||||
display: none;
|
||||
background-color: var(--nav-bg);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: v.$mp-2;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
text-align: center;
|
||||
|
||||
font-family: var(--heading-font-family);
|
||||
text-transform: uppercase;
|
||||
color: var(--nav-fg) !important;
|
||||
|
||||
.icon:first-child, .icon + span {
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--nav-hv-bg);
|
||||
color: var(--nav-hv-fg);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--nav-active-bg);
|
||||
color: var(--nav-active-fg) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
.dropdown-content {
|
||||
font-size: v.$text-size;
|
||||
min-width: 15rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.primary {
|
||||
height: var(--nav-primary-height);
|
||||
|
||||
.nav-menu {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
display: inline-block;
|
||||
padding: v.$mp-3;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: var(--nav-fs);
|
||||
font-weight: v.$weight-bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background-color: var(--nav-secondary-bg);
|
||||
//position: absolute;
|
||||
//width: 100%;
|
||||
//box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
|
||||
|
||||
justify-content: right;
|
||||
//display: none;
|
||||
|
||||
.nav.primary:hover + &,
|
||||
&:hover {
|
||||
display: flex;
|
||||
top: var(--nav-primary-height);
|
||||
left: 0rem;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: var(--nav-2-fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- breadcrumbs
|
||||
.breadcrumbs {
|
||||
text-align: right;
|
||||
padding: v.$mp-3 0rem;
|
||||
font-size: v.$text-size-smaller;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:empty { display: none; }
|
||||
|
||||
a + a {
|
||||
padding-left: 0;
|
||||
|
||||
&:before {
|
||||
content: "/";
|
||||
margin: 0 v.$mp-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: v.$screen-normal) {
|
||||
.page {
|
||||
margin-top: var(--nav-primary-height);
|
||||
}
|
||||
|
||||
.navs {
|
||||
z-index: 100000;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
.nav:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.nav + .nav {
|
||||
flex-grow: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
justify-content: space-between;
|
||||
|
||||
.burger {
|
||||
display: unset;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: var(--nav-secondary-bg);
|
||||
left: 0;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
box-shadow: 0em 0.5em 0.5em rgba(0,0,0,0.05);
|
||||
|
||||
.nav-item {
|
||||
display: block;
|
||||
font-weight: v.$weight-normal;
|
||||
font-size: var(--nav-fs);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu:not(.active) {
|
||||
display: none !important
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nav li {
|
||||
list-style: none;
|
||||
|
||||
a, .button {
|
||||
font-size: v.$text-size-medium;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.nav-urls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
margin-top: v.$mp-3;
|
||||
text-align: right;
|
||||
|
||||
> a:only-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.urls {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: v.$mp-3;
|
||||
justify-content: center;
|
||||
|
||||
a:not(:last-child) {
|
||||
margin-right: v.$mp-3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.left {
|
||||
flex-grow: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex-grow: 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- page header
|
||||
.header {
|
||||
&.preview-header {
|
||||
//display: flex;
|
||||
align-items: start;
|
||||
gap: v.$mp-3;
|
||||
min-height: unset;
|
||||
padding-top: v.$mp-3 !important;
|
||||
|
||||
}
|
||||
|
||||
.headings {
|
||||
width: unset;
|
||||
flex-grow: 1;
|
||||
padding-top: 0 !important;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.has-cover {
|
||||
min-height: calc( var(--header-height) / 3 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.header-cover:not(:only-child) {
|
||||
float: right;
|
||||
position: relative;
|
||||
z-index: 30;
|
||||
background-color: var(--body-bg);
|
||||
margin: 0 0 v.$mp-4 v.$mp-4;
|
||||
|
||||
.cover {
|
||||
max-width: calc(var(--header-height) * 2);
|
||||
height: var(--header-height);
|
||||
}
|
||||
}
|
||||
.header-cover:only-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-small) {
|
||||
.container.header {
|
||||
width: calc( 100% - v.$mp-2 );
|
||||
|
||||
.headings {
|
||||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.header-cover {
|
||||
float: none;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cover {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: calc(var(--cover-h) * 1);
|
||||
max-width: calc(var(--cover-w) * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- ---- detail
|
||||
.page-content {
|
||||
margin-top: v.$mp-6;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: v.$mp-6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- ---- list
|
||||
.list-item {
|
||||
&.logs {
|
||||
.track {
|
||||
margin-right: v.$mp-3;
|
||||
.icon {
|
||||
margin-right: v.$mp-2;
|
||||
color: var(--secondary-color-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3n):not(.wide) .media,
|
||||
{
|
||||
border-color: var(--main-color-dark) !important;
|
||||
}
|
||||
|
||||
&:nth-child(3n+1):not(.wide) .media,
|
||||
{
|
||||
border-color: var(--secondary-color-dark) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- responsive
|
||||
@media screen and (max-width: v.$screen-normal) {
|
||||
.page .container {
|
||||
margin-left: v.$mp-4;
|
||||
margin-right: v.$mp-4;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: v.$screen-small) {
|
||||
.page .container {
|
||||
margin-left: v.$mp-2;
|
||||
margin-right: v.$mp-2;
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
@charset "utf-8";
|
||||
|
||||
$black: #000;
|
||||
$white: #fff;
|
||||
$red: #e00;
|
||||
$red-dark: #b00;
|
||||
$green: #0e0;
|
||||
$green-dark: #0b0;
|
||||
$grey-light: #ddd;
|
||||
|
||||
$mp-1: 0.2rem;
|
||||
$mp-1e: 0.2em;
|
||||
$mp-2: 0.4rem;
|
||||
$mp-2e: 0.4em;
|
||||
$mp-3: 0.6rem;
|
||||
$mp-3e: 0.6em;
|
||||
$mp-4: 1.2rem;
|
||||
$mp-4e: 1.2em;
|
||||
$mp-5: 1.6rem;
|
||||
$mp-5e: 1.6em;
|
||||
$mp-6: 2rem;
|
||||
$mp-6e: 2em;
|
||||
$mp-7: 4rem;
|
||||
$mp-7e: 4em;
|
||||
|
||||
$text-size-small: 0.6rem;
|
||||
$text-size-smaller: 0.8rem;
|
||||
$text-size: 1rem;
|
||||
$text-size-2: 1.2rem;
|
||||
$text-size-medium: 1.4rem;
|
||||
$text-size-bigger: 1.6rem;
|
||||
$text-size-big: 2rem;
|
||||
|
||||
$h1-size: 40px;
|
||||
$h2-size: 32px;
|
||||
$h3-size: 28px;
|
||||
$h4-size: 24px;
|
||||
$h5-size: 20px;
|
||||
$h6-size: 14px;
|
||||
|
||||
$weight-light: 100;
|
||||
$weight-lighter: 300;
|
||||
$weight-normal: 400;
|
||||
$weight-bolder: 500;
|
||||
$weight-bold: 700;
|
||||
|
||||
$screen-very-small: 400px;
|
||||
$screen-small: 600px;
|
||||
$screen-smaller: 900px;
|
||||
$screen-normal: 1024px;
|
||||
$screen-wider: 1280px;
|
||||
$screen-wide: 1380px;
|
|
@ -1,35 +0,0 @@
|
|||
@import 'v-calendar/style.css';
|
||||
// @import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||
|
||||
// ---- bulma
|
||||
$body-color: #000;
|
||||
$title-color: #000;
|
||||
$modal-content-width: 80%;
|
||||
|
||||
|
||||
@import "bulma/sass/utilities/_all.sass";
|
||||
|
||||
|
||||
@import "bulma/sass/base/_all";
|
||||
@import "bulma/sass/components/dropdown";
|
||||
// @import "bulma/sass/components/card";
|
||||
@import "bulma/sass/components/media";
|
||||
@import "bulma/sass/components/message";
|
||||
@import "bulma/sass/components/modal";
|
||||
//@import "bulma/sass/components/pagination";
|
||||
|
||||
@import "bulma/sass/form/_all";
|
||||
@import "bulma/sass/grid/_all";
|
||||
@import "bulma/sass/helpers/_all";
|
||||
@import "bulma/sass/layout/_all";
|
||||
@import "bulma/sass/elements/box";
|
||||
// @import "bulma/sass/elements/button";
|
||||
@import "bulma/sass/elements/container";
|
||||
// @import "bulma/sass/elements/content";
|
||||
@import "bulma/sass/elements/icon";
|
||||
// @import "bulma/sass/elements/image";
|
||||
// @import "bulma/sass/elements/notification";
|
||||
// @import "bulma/sass/elements/progress";
|
||||
@import "bulma/sass/elements/table";
|
||||
@import "bulma/sass/elements/tag";
|
||||
//@import "bulma/sass/elements/title";
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* Run function with provided args only if document is not hidden
|
||||
*/
|
||||
export function setEcoTimeout(func, ...args) {
|
||||
return setTimeout((...args) => {
|
||||
!document.hidden && func(...args)
|
||||
}, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run function at specific interval only if document is not hidden
|
||||
*/
|
||||
export function setEcoInterval(func, ...args) {
|
||||
return setInterval((...args) => {
|
||||
!document.hidden && func(...args)
|
||||
}, ...args)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import {createApp} from 'vue'
|
||||
|
||||
import PageLoad from './pageLoad'
|
||||
import BackgroundLoad from './backgroundLoad'
|
||||
|
||||
|
||||
/**
|
||||
* Handles loading Vue js app on page load.
|
||||
*/
|
||||
export default class VueLoader {
|
||||
constructor({el=null, props={}, ...appConfig}={}, loaderOptions={}) {
|
||||
this.appConfig = appConfig
|
||||
this.appConfig.el = el
|
||||
this.props = props
|
||||
this.pageLoad = new PageLoad(el, loaderOptions)
|
||||
|
||||
this.pageLoad.onPreMount = event => this.onPreMount(event)
|
||||
this.pageLoad.onMount = event => this.onMount(event)
|
||||
|
||||
this.backgroundLoad = new BackgroundLoad()
|
||||
}
|
||||
|
||||
enable(hotReload=true) {
|
||||
hotReload && this.pageLoad.enable(document.body)
|
||||
this.mount()
|
||||
}
|
||||
|
||||
mount() {
|
||||
if(this.app)
|
||||
this.unmount()
|
||||
|
||||
const app = createApp(this.appConfig, this.props)
|
||||
app.config.globalProperties.window = window
|
||||
this.vm = app.mount(this.pageLoad.el)
|
||||
this.app = app
|
||||
}
|
||||
|
||||
unmount() {
|
||||
if(!this.app)
|
||||
return
|
||||
try { this.app.unmount() }
|
||||
catch(_) { null }
|
||||
this.app = null
|
||||
this.vm = null
|
||||
this.pageLoad.reset()
|
||||
}
|
||||
|
||||
onPreMount() { this.unmount() }
|
||||
onMount() { this.mount() }
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import { resolve } from 'path'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
build: {
|
||||
outDir: "../static/aircox/",
|
||||
sourcemap: true,
|
||||
|
||||
rollupOptions: {
|
||||
external: ['vue',],
|
||||
input: {
|
||||
public: "src/public.js",
|
||||
admin: "src/admin.js",
|
||||
},
|
||||
output: {
|
||||
globals: {
|
||||
vue: 'Vue',
|
||||
},
|
||||
assetFileNames: "[name].[ext]",
|
||||
chunkFileNames: "[name].js",
|
||||
entryFileNames: "[name].js",
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
},
|
||||
},
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.json', '.vue'],
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
|
@ -1,48 +0,0 @@
|
|||
// Enable styling body background while using vue hotreload
|
||||
// Tags with side effect (<script> and <style>) are ignored in client component templates.
|
||||
|
||||
|
||||
class BackgroundLoad {
|
||||
// change background style on load
|
||||
// and also on vuejs pageLoaded event
|
||||
|
||||
constructor () {
|
||||
let url = new URL(document.location);
|
||||
this.path = url.pathname;
|
||||
this.update();
|
||||
document.addEventListener("pageLoaded", this.handlePageLoad.bind(this), false);
|
||||
}
|
||||
|
||||
handlePageLoad (e) {
|
||||
this.path = e.detail;
|
||||
this.update();
|
||||
}
|
||||
|
||||
update () {
|
||||
let theme = this.get_theme_name();
|
||||
document.body.className = theme;
|
||||
|
||||
// home page uses different theme
|
||||
if (this.path == "/") {
|
||||
document.body.classList.add('home');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
get_theme_name () {
|
||||
var currentTime = new Date().getHours();
|
||||
if (document.body) {
|
||||
if (3 <= currentTime && currentTime <15) {
|
||||
return "yellow";
|
||||
}
|
||||
else {
|
||||
return "blue";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
window.onload = function() {
|
||||
let backgroundLoad = new BackgroundLoad();
|
||||
}
|
Before Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 1.9 MiB |
BIN
radiocampus/static/radiocampus/fonts/CampusGroteskv8-Regular.otf
Normal file
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 168 KiB |
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 77 KiB |
|
@ -1,250 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1190.6 841.9">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #000;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-2" d="M215.8-748.5c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-28.5l59,62.9h-25.2l-56.9-62.9h-12.6v62.9h-18.8v-145.5h81.1ZM153.5-684.1h64c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.3-21.6-22.5h-64v46.2Z"/>
|
||||
<path class="cls-2" d="M370.4-651.2h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM363.2-669.4l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M489.9-748.5c36.8.2,60.1,30.2,60.1,70.8s-19,74.7-61.4,74.7h-60.6v-145.5h61.8ZM446.9-621.2h45.6c25.5,0,39.4-22,39.4-55.4s-16.1-53.5-40.2-53.7h-44.7v109.1h0Z"/>
|
||||
<path class="cls-2" d="M657.2-620.8v17.8h-94.1v-17.8h38.5v-110h-32.1v-17.8h81.3v17.8h-31.5v110h37.9Z"/>
|
||||
</g>
|
||||
<path class="cls-2" d="M369.4-476.5h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM362.1-494.7l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M678.2-573.6c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-59.9v62.9h-18.8v-145.5h76.8ZM620.2-509.2h59.7c13.3-.2,21.6-9.6,21.6-23.7s-8.3-22.2-21.6-22.5h-59.7v46.2Z"/>
|
||||
<path class="cls-2" d="M849.6-471.1c-.2,29.8-20.5,47.3-54.8,47.3s-57.8-18.2-57.8-45.4v-104.4h19v102.1c.2,18.8,14.1,29.1,37.7,29.1s36.6-10.3,36.8-29.1v-102.1h19v102.5h0Z"/>
|
||||
<path class="cls-2" d="M929.4-442c17.8,0,30.8-6.8,30.8-21.4s-10.3-18.8-19.5-22c-11.8-4.1-21.4-5.6-35.1-9.6-17.1-5.1-29.7-19.7-29.7-40s15.6-42.6,47.7-42.6,38.1,4.1,55,22.5l-12.8,13.1c-15.2-14.8-34.2-17.3-43.2-17.3-17.3,0-27.8,11.1-27.8,23.5s6,19.3,20.1,23.3c7.3,2.1,18.6,5.4,34.4,10.1,23.5,7.1,29.7,21.4,29.7,38.7s-19.9,40-49.6,40-43.2-7.7-56.1-25.9l15.6-11.6c10.7,13.9,24.4,19.3,40.4,19.3Z"/>
|
||||
<path class="cls-2" d="M536.2-272.3v18.2h-103.2v-145.9h18.2v127.7h85Z"/>
|
||||
<path class="cls-2" d="M253.4-296.5c0,26.3-19.1,43-46,42.4l-9-.2h-63.4v-145.6h69.8c27.2,1.1,39,18.2,39,38.5s-3.9,21-9.2,27c10.7,7.3,18.8,20.8,18.8,37.9ZM153.2-340.8h51.8c14.8,0,20.5-8.6,20.5-20.3s-5.6-20.6-24.2-20.6h-48.2v40.9ZM235.2-298c0-13.5-7.3-24.6-24.8-24.6h-57.2v50.1h55c19.7,0,27-10.5,27-25.5Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M301.9-381.8c0,10.1-8.2,18.3-18.3,18.3s-18.3-8.2-18.3-18.3,8.2-18.3,18.3-18.3,18.3,8.2,18.3,18.3Z"/>
|
||||
<circle class="cls-2" cx="393.6" cy="-381.8" r="18.3"/>
|
||||
<path class="cls-2" d="M338.6-345.3c0,10.1-8.2,18.3-18.3,18.4-10.1,0-18.3-8.2-18.4-18.3,0-10.1,8.2-18.3,18.3-18.4,10.1,0,18.3,8.2,18.4,18.3Z"/>
|
||||
<circle class="cls-2" cx="356.9" cy="-345.3" r="18.3"/>
|
||||
<path class="cls-2" d="M338.6-308.6c0,10.1-8.2,18.3-18.3,18.4-10.1,0-18.3-8.2-18.4-18.3,0-10.1,8.2-18.3,18.3-18.4,10.1,0,18.3,8.2,18.4,18.3Z"/>
|
||||
<circle class="cls-2" cx="357" cy="-308.7" r="18.3"/>
|
||||
<path class="cls-2" d="M301.9-271.8c0,10.1-8.2,18.3-18.3,18.3s-18.3-8.2-18.3-18.3,8.2-18.3,18.3-18.3c10.1,0,18.3,8.2,18.3,18.3Z"/>
|
||||
<circle class="cls-2" cx="393.6" cy="-271.8" r="18.3"/>
|
||||
</g>
|
||||
<polygon class="cls-2" points="428.4 -573.9 428.4 -428.6 439 -428.6 439 -557.1 480.8 -557.1 480.8 -428.6 501.2 -428.6 501.2 -557.1 544.2 -557.1 544.2 -428.6 569.8 -428.6 569.8 -573.9 428.4 -573.9"/>
|
||||
<polygon class="cls-2" points="253.5 -428.2 172.8 -428.2 172.8 -463.1 134.9 -463.1 134.9 -549.4 173.3 -549.4 173.3 -574.4 216.5 -574.4 216.5 -557.4 190.3 -557.4 190.3 -532.4 151.9 -532.4 151.9 -480.1 189.8 -480.1 189.8 -445.2 253.5 -445.2 253.5 -428.2"/>
|
||||
<path class="cls-2" d="M809.9-693.8c6.1,4.5,12.4,8.6,18.8,12.5v-21.3c-2.8-1.8-5.5-3.7-8.1-5.7-16-11.8-29.8-25.3-41.5-40.3h-22.2c14.1,20.9,31.9,39.2,53,54.8Z"/>
|
||||
<path class="cls-2" d="M695.8-655.8c-7.1-5.4-14.5-10.4-22.1-14.9v21.3c3.8,2.5,7.5,5.2,11.2,7.9,14.8,11.3,27.8,24.1,39,38.4h22.3c-13.7-20-30.6-37.7-50.4-52.8Z"/>
|
||||
<path class="cls-2" d="M828.7-648.7v-21.6c-3.8,2.4-7.5,4.8-11.1,7.4-24.3,17-44.8,37.5-60.5,60h22.5c13.7-17.2,30.4-32.7,49.1-45.7Z"/>
|
||||
<path class="cls-2" d="M746.5-748.7h-22.1c-13.9,18.4-31.1,34.2-50.8,47v21.1c29.4-17.1,54.4-40.3,72.8-68.1Z"/>
|
||||
<path class="cls-2" d="M760-347.3c0,69.6-16.9,97.2-53.1,97.2s-39.4-10.3-47.7-25.7l14.1-10.1c6.6,12.2,17.1,18,34.5,18s32.3-12.4,34.5-44.3c-9,7.9-21.2,13.1-34.7,13.1-31.7,0-52.2-24-52.2-48.2s20.8-56.9,52.2-56.9c31.3,0,52.4,24.2,52.4,56.9ZM743.8-350.5c0-23.1-17.6-34.2-36.2-34.2s-34.7,11.1-34.7,34.2,14.8,33.4,34.7,33.4c20.8,0,36.2-13.5,36.2-33.4Z"/>
|
||||
<path class="cls-2" d="M875.6-272.8v18.4h-92.5v-18.4c57.4-42.6,71.7-60.8,71.7-83.7s-11.3-28.9-31.7-28.9-33,11.3-26.8,39.2l-17.1,7.9c-9.2-30.2,4.7-65.9,45.6-65.9s47.3,18.2,47.3,43.9-21,59.5-62.9,87.6h66.4Z"/>
|
||||
<path class="cls-2" d="M907.7-276.4v22h-22v-22h22Z"/>
|
||||
<path class="cls-2" d="M960.6-399.9h18.4v145.8h-18.4v-122.7l-39,37.9v-22.7l39-38.3Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M1448.4-1634.5c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-28.5l59,62.9h-25.2l-56.9-62.9h-12.6v62.9h-18.8v-145.5h81.1ZM1386.1-1570.1h64c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.3-21.6-22.5h-64v46.2Z"/>
|
||||
<path class="cls-2" d="M1603-1537.1h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM1595.7-1555.3l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M1722.4-1634.5c36.8.2,60.1,30.2,60.1,70.8s-19,74.7-61.4,74.7h-60.6v-145.5h61.8ZM1679.4-1507.2h45.6c25.5,0,39.4-22,39.4-55.4s-16.1-53.5-40.2-53.7h-44.7v109.1h0Z"/>
|
||||
<path class="cls-2" d="M1889.8-1506.7v17.8h-94.1v-17.8h38.5v-110h-32.1v-17.8h81.3v17.8h-31.5v110h37.9Z"/>
|
||||
<path class="cls-2" d="M1602-1362.4h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM1594.7-1380.6l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M1910.8-1459.6c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-59.9v62.9h-18.8v-145.5h76.8ZM1852.8-1395.2h59.7c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.2-21.6-22.5h-59.7v46.2Z"/>
|
||||
<path class="cls-2" d="M2082.2-1357.1c-.2,29.7-20.5,47.3-54.8,47.3s-57.8-18.2-57.8-45.4v-104.4h19v102.1c.2,18.8,14.1,29.1,37.7,29.1s36.6-10.3,36.8-29.1v-102.1h19v102.5h0Z"/>
|
||||
<path class="cls-2" d="M2162-1328c17.8,0,30.8-6.8,30.8-21.4s-10.3-18.8-19.5-22c-11.8-4.1-21.4-5.6-35.1-9.6-17.1-5.1-29.7-19.7-29.7-40s15.6-42.6,47.7-42.6,38.1,4.1,55,22.5l-12.8,13.1c-15.2-14.8-34.2-17.3-43.2-17.3-17.3,0-27.8,11.1-27.8,23.5s6,19.3,20.1,23.3c7.3,2.1,18.6,5.4,34.4,10.1,23.5,7.1,29.7,21.4,29.7,38.7s-19.9,40-49.6,40-43.2-7.7-56.1-25.9l15.6-11.6c10.7,13.9,24.4,19.3,40.4,19.3Z"/>
|
||||
<polygon class="cls-2" points="1660.9 -1459.9 1660.9 -1314.5 1671.6 -1314.5 1671.6 -1443.1 1713.3 -1443.1 1713.3 -1314.5 1733.8 -1314.5 1733.8 -1443.1 1776.7 -1443.1 1776.7 -1314.5 1802.4 -1314.5 1802.4 -1459.9 1660.9 -1459.9"/>
|
||||
<polygon class="cls-2" points="1486.1 -1314.1 1405.3 -1314.1 1405.3 -1349.1 1367.5 -1349.1 1367.5 -1435.3 1405.9 -1435.3 1405.9 -1460.4 1449 -1460.4 1449 -1443.4 1422.9 -1443.4 1422.9 -1418.3 1384.5 -1418.3 1384.5 -1366.1 1422.3 -1366.1 1422.3 -1331.1 1486.1 -1331.1 1486.1 -1314.1"/>
|
||||
<path class="cls-2" d="M2050.1-1548.9c-24.3,17-44.8,37.5-60.5,60h22.5c13.7-17.2,30.4-32.7,49.1-45.7v-21.6c-3.8,2.4-7.5,4.8-11.1,7.4Z"/>
|
||||
<path class="cls-2" d="M2042.5-1579.8c6.1,4.5,12.4,8.7,18.8,12.6v-21.3c-2.8-1.8-5.5-3.7-8.1-5.7-16-11.8-29.8-25.3-41.5-40.3h-22.2c14.1,20.9,31.9,39.2,53,54.8Z"/>
|
||||
<path class="cls-2" d="M1979.1-1634.6h-22.1c-13.9,18.4-31.1,34.2-50.8,47v21.1c29.5-17.1,54.4-40.3,72.9-68.1Z"/>
|
||||
<path class="cls-2" d="M1928.3-1541.7c-7.1-5.4-14.5-10.3-22.1-14.9v21.3c3.8,2.5,7.5,5.2,11.2,7.9,14.8,11.3,27.8,24.1,39,38.4h22.3c-13.7-20-30.6-37.7-50.4-52.8Z"/>
|
||||
<path class="cls-2" d="M1408.8-1192.9c0,10.2-8.2,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5,0-10.2,8.2-18.5,18.5-18.5,10.2,0,18.5,8.3,18.5,18.5Z"/>
|
||||
<path class="cls-2" d="M1445.8-1193c0,10.2-8.2,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5,0-10.2,8.3-18.5,18.5-18.5,10.2,0,18.5,8.3,18.5,18.5Z"/>
|
||||
<path class="cls-2" d="M1408.8-1155.9c0,10.2-8.3,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5,0-10.2,8.3-18.5,18.5-18.5,10.2,0,18.5,8.2,18.5,18.5Z"/>
|
||||
<path class="cls-2" d="M1445.8-1156c0,10.2-8.3,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5,0-10.2,8.2-18.5,18.5-18.5,10.2,0,18.5,8.2,18.5,18.5Z"/>
|
||||
<path class="cls-2" d="M1584.4-1182.4c0,26.3-19,43-46,42.4l-9-.2h-63.3v-145.5h69.8c27.2,1.1,38.9,18.2,38.9,38.5s-3.9,21-9.2,27c10.7,7.3,18.8,20.8,18.8,37.9ZM1484.2-1226.7h51.8c14.8,0,20.5-8.6,20.5-20.3s-5.6-20.5-24.2-20.5h-48.2v40.9ZM1566.2-1183.9c0-13.5-7.3-24.6-24.8-24.6h-57.1v50.1h55c19.7,0,27-10.5,27-25.5Z"/>
|
||||
<path class="cls-2" d="M1620.8-1267.5v40.9h77.2v18.2h-77.2v50.1h85.8v18.2h-104v-145.5h104v18.2h-85.8Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M1448.8-748.7c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-28.5l59,62.9h-25.2l-56.9-62.9h-12.6v62.9h-18.8v-145.5h81.1ZM1386.6-684.3h64c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.3-21.6-22.5h-64v46.2Z"/>
|
||||
<path class="cls-2" d="M1603.5-651.4h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM1596.2-669.5l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M1722.9-748.7c36.8.2,60.1,30.2,60.1,70.8s-19,74.7-61.4,74.7h-60.6v-145.5h61.8ZM1679.9-621.4h45.6c25.5,0,39.4-22,39.4-55.4s-16.1-53.5-40.2-53.7h-44.7v109.1h0Z"/>
|
||||
<path class="cls-2" d="M1890.3-621v17.8h-94.1v-17.8h38.5v-110h-32.1v-17.8h81.3v17.8h-31.5v110h37.9Z"/>
|
||||
<path class="cls-2" d="M1602.5-476.7h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM1595.2-494.9l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M1911.3-573.8c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-59.9v62.9h-18.8v-145.5h76.8ZM1853.3-509.4h59.7c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.2-21.6-22.5h-59.7v46.2Z"/>
|
||||
<path class="cls-2" d="M2082.6-471.3c-.2,29.7-20.5,47.3-54.8,47.3s-57.8-18.2-57.8-45.4v-104.4h19v102.1c.2,18.8,14.1,29.1,37.7,29.1s36.6-10.3,36.8-29.1v-102.1h19v102.5h0Z"/>
|
||||
<path class="cls-2" d="M2162.5-442.2c17.8,0,30.8-6.8,30.8-21.4s-10.3-18.8-19.5-22c-11.8-4.1-21.4-5.6-35.1-9.6-17.1-5.1-29.7-19.7-29.7-40s15.6-42.6,47.7-42.6,38.1,4.1,55,22.5l-12.8,13.1c-15.2-14.8-34.2-17.3-43.2-17.3-17.3,0-27.8,11.1-27.8,23.5s6,19.3,20.1,23.3c7.3,2.1,18.6,5.4,34.4,10.1,23.5,7.1,29.7,21.4,29.7,38.7s-19.9,40-49.6,40-43.2-7.7-56.1-25.9l15.6-11.6c10.7,13.9,24.4,19.3,40.4,19.3Z"/>
|
||||
<polygon class="cls-2" points="1661.4 -574.1 1661.4 -428.8 1672.1 -428.8 1672.1 -557.3 1713.8 -557.3 1713.8 -428.8 1734.2 -428.8 1734.2 -557.3 1777.2 -557.3 1777.2 -428.8 1802.8 -428.8 1802.8 -574.1 1661.4 -574.1"/>
|
||||
<polygon class="cls-2" points="1486.6 -428.4 1405.8 -428.4 1405.8 -463.3 1368 -463.3 1368 -549.6 1406.4 -549.6 1406.4 -574.6 1449.5 -574.6 1449.5 -557.6 1423.4 -557.6 1423.4 -532.6 1385 -532.6 1385 -480.3 1422.8 -480.3 1422.8 -445.4 1486.6 -445.4 1486.6 -428.4"/>
|
||||
<path class="cls-2" d="M2050.6-663.1c-24.3,17-44.8,37.5-60.5,60h22.5c13.7-17.2,30.4-32.7,49.1-45.7v-21.6c-3.8,2.4-7.5,4.8-11.1,7.4Z"/>
|
||||
<path class="cls-2" d="M2042.9-694c6.1,4.5,12.4,8.7,18.8,12.6v-21.3c-2.8-1.8-5.5-3.7-8.1-5.7-16-11.8-29.8-25.3-41.5-40.3h-22.2c14.1,20.9,31.9,39.2,53,54.8Z"/>
|
||||
<path class="cls-2" d="M1979.6-748.8h-22.1c-13.9,18.4-31.1,34.2-50.8,47v21.1c29.5-17.1,54.4-40.3,72.9-68.1Z"/>
|
||||
<path class="cls-2" d="M1928.8-655.9c-7.1-5.4-14.5-10.3-22.1-14.9v21.3c3.8,2.5,7.5,5.2,11.2,7.9,14.8,11.3,27.8,24.1,39,38.4h22.3c-13.7-20-30.6-37.7-50.4-52.8Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M1409.3-307.2c0,10.2-8.2,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5,0-10.2,8.3-18.5,18.5-18.5,10.2,0,18.5,8.3,18.5,18.5Z"/>
|
||||
<path class="cls-2" d="M1446.3-307.2c0,10.2-8.2,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5,0-10.2,8.3-18.5,18.5-18.5,10.2,0,18.5,8.2,18.5,18.5Z"/>
|
||||
<path class="cls-2" d="M1409.3-270.2c0,10.2-8.3,18.5-18.5,18.5-10.2,0-18.5-8.2-18.5-18.5s8.2-18.5,18.5-18.5c10.2,0,18.5,8.2,18.5,18.5Z"/>
|
||||
<circle class="cls-2" cx="1427.8" cy="-270.2" r="18.5"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M1584.9-296.6c0,26.3-19,43-46,42.4l-9-.2h-63.3v-145.5h69.8c27.2,1.1,38.9,18.2,38.9,38.5s-3.9,21-9.2,27c10.7,7.3,18.8,20.8,18.8,37.9ZM1484.7-340.9h51.8c14.8,0,20.5-8.6,20.5-20.3s-5.6-20.5-24.2-20.5h-48.1v40.9ZM1566.7-298.1c0-13.5-7.3-24.6-24.8-24.6h-57.1v50.1h55c19.7,0,27-10.5,27-25.5Z"/>
|
||||
<path class="cls-2" d="M1621.3-381.8v40.9h77.2v18.2h-77.2v50.1h85.8v18.2h-104v-145.5h104v18.2h-85.8Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M1993.6-346.7c0,69.6-16.9,97.2-53.1,97.2s-39.4-10.3-47.7-25.7l14.1-10.1c6.6,12.2,17.1,18,34.5,18s32.3-12.4,34.5-44.3c-9,7.9-21.2,13.1-34.7,13.1-31.7,0-52.2-24-52.2-48.2s20.8-56.9,52.2-56.9c31.3,0,52.4,24.2,52.4,56.9ZM1977.4-349.9c0-23.1-17.6-34.3-36.2-34.3s-34.7,11.1-34.7,34.3,14.8,33.4,34.7,33.4c20.8,0,36.2-13.5,36.2-33.4Z"/>
|
||||
<path class="cls-2" d="M2109.2-272.2v18.4h-92.5v-18.4c57.4-42.6,71.7-60.8,71.7-83.7s-11.3-28.9-31.7-28.9-33,11.3-26.8,39.2l-17.1,7.9c-9.2-30.2,4.7-65.9,45.6-65.9s47.3,18.2,47.3,43.9-21,59.5-62.9,87.6h66.4Z"/>
|
||||
<path class="cls-2" d="M2141.3-275.8v22h-22v-22h22Z"/>
|
||||
<path class="cls-2" d="M2194.2-399.3h18.4v145.8h-18.4v-122.7l-39,37.9v-22.7l39-38.3Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M3011.2-678.5c.2-.1.4-.2.6-.4h-1.2c.2.1.4.2.6.4Z"/>
|
||||
<path class="cls-2" d="M2744.4-663.2c-24.3,17-44.8,37.5-60.5,60h22.5c13.7-17.2,30.4-32.7,49.1-45.7v-21.6c-3.8,2.4-7.5,4.8-11.1,7.4Z"/>
|
||||
<path class="cls-2" d="M2736.8-694.1c6.1,4.5,12.4,8.7,18.8,12.6v-21.3c-2.8-1.8-5.5-3.7-8.1-5.7-16-11.8-29.8-25.3-41.5-40.3h-22.2c14.1,20.9,31.9,39.2,53,54.8Z"/>
|
||||
<path class="cls-2" d="M2673.4-748.9h-22.1c-13.9,18.4-31.1,34.2-50.8,47v21.1c29.5-17.1,54.4-40.3,72.8-68.1Z"/>
|
||||
<path class="cls-2" d="M2622.6-656c-7.1-5.4-14.5-10.3-22.1-14.9v21.3c3.8,2.5,7.5,5.2,11.2,7.9,14.8,11.3,27.8,24.1,39,38.4h22.3c-13.7-20-30.6-37.7-50.4-52.8Z"/>
|
||||
<polygon class="cls-2" points="2912.4 -602.8 2831.7 -602.8 2831.7 -637.7 2793.8 -637.7 2793.8 -723.9 2832.2 -723.9 2832.2 -749 2875.4 -749 2875.4 -732 2849.2 -732 2849.2 -706.9 2810.8 -706.9 2810.8 -654.7 2848.7 -654.7 2848.7 -619.8 2912.4 -619.8 2912.4 -602.8"/>
|
||||
<polygon class="cls-2" points="2944.5 -748 2944.5 -602.6 2955.1 -602.6 2955.1 -731.2 2996.9 -731.2 2996.9 -602.6 3017.3 -602.6 3017.3 -731.2 3060.3 -731.2 3060.3 -602.6 3085.9 -602.6 3085.9 -748 2944.5 -748"/>
|
||||
<path class="cls-2" d="M3162.6-733.1c0,10.5-8.5,19.1-19.1,19.1s-19.1-8.5-19.1-19.1,8.5-19.1,19.1-19.1,19.1,8.5,19.1,19.1Z"/>
|
||||
<circle class="cls-2" cx="3258" cy="-733.1" r="19.1"/>
|
||||
<path class="cls-2" d="M3200.7-695.1c0,10.5-8.5,19.1-19,19.1s-19.1-8.5-19.1-19c0-10.5,8.5-19.1,19-19.1,10.5,0,19.1,8.5,19.1,19Z"/>
|
||||
<circle class="cls-2" cx="3219.8" cy="-695.1" r="19.1"/>
|
||||
<path class="cls-2" d="M3200.8-656.9c0,10.5-8.5,19.1-19,19.1-10.5,0-19.1-8.5-19.1-19s8.5-19.1,19-19.1,19.1,8.5,19.1,19Z"/>
|
||||
<circle class="cls-2" cx="3219.9" cy="-656.9" r="19.1"/>
|
||||
<path class="cls-2" d="M3162.6-618.6c0,10.5-8.5,19.1-19.1,19.1s-19.1-8.5-19.1-19.1,8.5-19.1,19.1-19.1,19.1,8.5,19.1,19.1Z"/>
|
||||
<circle class="cls-2" cx="3258" cy="-618.6" r="19.1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-2" d="M2765-1536.3c-27.7,19.4-51.1,42.7-69,68.4h25.6c15.6-19.6,34.6-37.3,56-52.1v-24.7c-4.3,2.7-8.5,5.5-12.7,8.4Z"/>
|
||||
<path class="cls-2" d="M2756.2-1571.5c6.9,5.1,14.1,9.9,21.5,14.3v-24.3c-3.1-2.1-6.2-4.3-9.3-6.5-18.2-13.4-34-28.8-47.3-46h-25.3c16.1,23.8,36.3,44.8,60.4,62.5Z"/>
|
||||
<path class="cls-2" d="M2683.9-1634h-25.2c-15.9,20.9-35.5,39-57.9,53.6v24.1c33.6-19.5,62-45.9,83.1-77.7Z"/>
|
||||
<path class="cls-2" d="M2626-1528.1c-8.1-6.1-16.5-11.8-25.2-17v24.3c4.3,2.9,8.6,5.9,12.7,9.1,16.9,12.8,31.7,27.5,44.5,43.8h25.4c-15.7-22.8-34.9-43-57.5-60.2Z"/>
|
||||
<polygon class="cls-2" points="2951.8 -1466.7 2859.7 -1466.7 2859.7 -1506.5 2816.6 -1506.5 2816.6 -1604.9 2860.3 -1604.9 2860.3 -1633.5 2909.6 -1633.5 2909.6 -1614.1 2879.7 -1614.1 2879.7 -1585.5 2835.9 -1585.5 2835.9 -1525.9 2879.1 -1525.9 2879.1 -1486.1 2951.8 -1486.1 2951.8 -1466.7"/>
|
||||
<polygon class="cls-2" points="2600.8 -1442.6 2600.8 -1276.8 2612.9 -1276.8 2612.9 -1423.4 2660.5 -1423.4 2660.5 -1276.8 2683.9 -1276.8 2683.9 -1423.4 2732.9 -1423.4 2732.9 -1276.8 2762.1 -1276.8 2762.1 -1442.6 2600.8 -1442.6"/>
|
||||
<path class="cls-2" d="M2854.6-1421c0,11.5-9.4,20.9-20.9,20.9s-20.9-9.4-20.9-20.9,9.4-20.9,20.9-20.9c11.5,0,20.9,9.4,20.9,20.9Z"/>
|
||||
<circle class="cls-2" cx="2959.1" cy="-1421" r="20.9"/>
|
||||
<path class="cls-2" d="M2896.4-1379.4c0,11.5-9.3,20.9-20.9,20.9-11.5,0-20.9-9.3-20.9-20.9,0-11.5,9.3-20.9,20.9-20.9,11.5,0,20.9,9.3,20.9,20.9Z"/>
|
||||
<path class="cls-2" d="M2938.2-1379.5c0,11.5-9.3,20.9-20.9,20.9-11.5,0-20.9-9.3-20.9-20.9,0-11.5,9.3-20.9,20.9-20.9,11.5,0,20.9,9.3,20.9,20.9Z"/>
|
||||
<path class="cls-2" d="M2896.5-1337.6c0,11.5-9.3,20.9-20.9,20.9-11.5,0-20.9-9.3-20.9-20.9,0-11.5,9.3-20.9,20.9-20.9,11.5,0,20.9,9.3,20.9,20.9Z"/>
|
||||
<circle class="cls-2" cx="2917.4" cy="-1337.6" r="20.9"/>
|
||||
<path class="cls-2" d="M2854.6-1295.6c0,11.5-9.4,20.9-20.9,20.9s-20.9-9.4-20.9-20.9,9.4-20.9,20.9-20.9c11.5,0,20.9,9.4,20.9,20.9Z"/>
|
||||
<path class="cls-2" d="M2980-1295.6c0,11.5-9.4,20.9-20.9,20.9s-20.9-9.4-20.9-20.9,9.4-20.9,20.9-20.9c11.5,0,20.9,9.4,20.9,20.9Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-2" d="M215.5-1634c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-28.5l59,62.9h-25.2l-56.9-62.9h-12.6v62.9h-18.8v-145.5h81.1ZM153.2-1569.6h64c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.3-21.6-22.5h-64v46.2Z"/>
|
||||
<path class="cls-2" d="M370.1-1536.7h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM362.8-1554.9l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M489.5-1634c36.8.2,60.1,30.2,60.1,70.8s-19,74.7-61.4,74.7h-60.6v-145.5h61.8ZM446.5-1506.7h45.6c25.5,0,39.4-22,39.4-55.4s-16-53.5-40.2-53.7h-44.7v109.1h0Z"/>
|
||||
<path class="cls-2" d="M656.9-1506.3v17.8h-94.1v-17.8h38.5v-110h-32.1v-17.8h81.3v17.8h-31.5v110h37.9Z"/>
|
||||
</g>
|
||||
<path class="cls-2" d="M369.1-1362h-66.3l-19.5,48.1h-19.3l58.4-145.5h26.7l58.4,145.5h-19.3l-19.3-48.1ZM361.8-1380.2l-22.7-56.5-3.2-8.1-3.2,8.1-22.7,56.5h51.8Z"/>
|
||||
<path class="cls-2" d="M677.9-1459.1c24.6.2,40.9,16,40.9,39.6s-15.6,43-38.9,43h-59.9v62.9h-18.8v-145.5h76.8ZM619.9-1394.7h59.7c13.3-.2,21.6-9.6,21.6-23.8s-8.3-22.2-21.6-22.5h-59.7v46.2Z"/>
|
||||
<path class="cls-2" d="M849.3-1356.6c-.2,29.7-20.5,47.3-54.8,47.3s-57.8-18.2-57.8-45.4v-104.4h19v102.1c.2,18.8,14.1,29.1,37.7,29.1s36.6-10.3,36.8-29.1v-102.1h19v102.5h0Z"/>
|
||||
<path class="cls-2" d="M929.1-1327.5c17.8,0,30.8-6.8,30.8-21.4s-10.3-18.8-19.5-22c-11.8-4.1-21.4-5.6-35.1-9.6-17.1-5.1-29.7-19.7-29.7-40s15.6-42.6,47.7-42.6,38.1,4.1,55,22.5l-12.8,13.1c-15.2-14.8-34.2-17.3-43.2-17.3-17.3,0-27.8,11.1-27.8,23.5s6,19.3,20.1,23.3c7.3,2.1,18.6,5.4,34.4,10.1,23.5,7.1,29.7,21.4,29.7,38.7s-19.9,40-49.6,40-43.2-7.7-56.1-25.9l15.6-11.6c10.7,13.9,24.4,19.3,40.4,19.3Z"/>
|
||||
<path class="cls-2" d="M535.8-1157.8v18.2h-103.2v-145.9h18.2v127.7h85Z"/>
|
||||
<path class="cls-2" d="M253.1-1182c0,26.3-19.1,43-46,42.4l-9-.2h-63.4v-145.6h69.8c27.2,1.1,39,18.2,39,38.5s-3.9,21-9.2,27c10.7,7.3,18.8,20.8,18.8,37.9ZM152.9-1226.3h51.8c14.8,0,20.5-8.6,20.5-20.3s-5.6-20.6-24.2-20.6h-48.2v40.9h0ZM234.9-1183.5c0-13.5-7.3-24.6-24.8-24.6h-57.2v50.1h55c19.7,0,27-10.5,27-25.5Z"/>
|
||||
<g>
|
||||
<circle class="cls-2" cx="283.3" cy="-1267.3" r="18.3"/>
|
||||
<circle class="cls-2" cx="393.3" cy="-1267.3" r="18.3"/>
|
||||
<path class="cls-2" d="M338.3-1230.8c0,10.1-8.2,18.3-18.3,18.4-10.1,0-18.3-8.2-18.4-18.3,0-10.1,8.2-18.3,18.3-18.4,10.1,0,18.3,8.2,18.4,18.3Z"/>
|
||||
<circle class="cls-2" cx="356.6" cy="-1230.8" r="18.3"/>
|
||||
<path class="cls-2" d="M338.3-1194.2c0,10.1-8.2,18.3-18.3,18.4-10.1,0-18.3-8.2-18.4-18.3,0-10.1,8.2-18.3,18.3-18.4,10.1,0,18.3,8.2,18.4,18.3Z"/>
|
||||
<circle class="cls-2" cx="356.6" cy="-1194.2" r="18.3"/>
|
||||
<circle class="cls-2" cx="283.3" cy="-1157.3" r="18.3"/>
|
||||
<circle class="cls-2" cx="393.3" cy="-1157.3" r="18.3"/>
|
||||
</g>
|
||||
<polygon class="cls-2" points="253.2 -1313.7 172.4 -1313.7 172.4 -1348.6 134.6 -1348.6 134.6 -1434.9 173 -1434.9 173 -1459.9 216.2 -1459.9 216.2 -1442.9 190 -1442.9 190 -1417.9 151.6 -1417.9 151.6 -1365.6 189.4 -1365.6 189.4 -1330.7 253.2 -1330.7 253.2 -1313.7"/>
|
||||
<path class="cls-2" d="M756.8-1487.9h22.5c0,0,.1-.2.2-.3h-22.6c0,0-.1.2-.2.3Z"/>
|
||||
<path class="cls-2" d="M673.4-1587.1v21.2c29.4-17.1,54.3-40.2,72.8-67.9h-22.2c-13.9,18.2-31,34-50.5,46.7Z"/>
|
||||
<path class="cls-2" d="M828.7-1533.8v-21.7c-3.7,2.3-7.4,4.8-11.1,7.3-24.4,17.1-44.9,37.6-60.6,60.1h22.6c13.7-17.2,30.4-32.6,49.1-45.7Z"/>
|
||||
<path class="cls-2" d="M695.5-1541c-7.1-5.4-14.5-10.4-22.1-14.9v21.3c3.8,2.5,7.5,5.2,11.2,7.9,14.8,11.3,27.9,24.2,39,38.5h22.4c-13.8-20.1-30.7-37.8-50.5-52.8Z"/>
|
||||
<path class="cls-2" d="M820.6-1593.7c-15.9-11.7-29.7-25.1-41.3-40.1h-22.3c14.1,20.8,31.8,39.1,52.9,54.6,6.1,4.5,12.3,8.6,18.8,12.5v-21.4c-2.7-1.8-5.4-3.7-8.1-5.7Z"/>
|
||||
<polygon class="cls-2" points="428 -1459.4 428 -1314.1 438.7 -1314.1 438.7 -1442.2 480.4 -1442.2 480.4 -1314.1 500.8 -1314.1 500.8 -1442.2 543.7 -1442.2 543.7 -1314.1 569.2 -1314.1 569.2 -1459.4 428 -1459.4"/>
|
||||
</g>
|
||||
<path class="cls-1" d="M1684.9,411c-11.5,0-20.9-9.3-20.9-20.9,0,11.5-9.3,20.9-20.9,20.9,11.5,0,20.9,9.3,20.9,20.9,0-11.5,9.3-20.9,20.9-20.9Z"/>
|
||||
<path class="cls-2" d="M1646.7,283.4v-39.9h-43.1v-25.6c.2,0,.4-.1.6-.2-.2,0-.4-.1-.6-.2v-33.6h43.8v-28.6h29.8v-19.4h-49.2v28.6h-43.8v98.3h43.1v39.9h92.1v-19.4h-72.7Z"/>
|
||||
<path class="cls-2" d="M1462.8,301.6h25.6c15.6-19.6,34.6-37.3,56-52.1v-24.7c-4.3,2.7-8.5,5.5-12.7,8.4-27.7,19.4-51.1,42.7-69,68.4Z"/>
|
||||
<path class="cls-2" d="M1535.1,181.5c-18.2-13.4-34-28.8-47.3-46h-25.3c16.1,23.8,36.3,44.8,60.4,62.5,3.5,2.6,7.1,5.1,10.8,7.5,3.5,2.3,7.1,4.6,10.7,6.8v-24.3c-3.1-2.1-6.2-4.3-9.3-6.5Z"/>
|
||||
<path class="cls-2" d="M1450.7,135.5h-25.2c-15.9,20.9-35.5,39-57.9,53.6v24.1c4.2-2.4,8.3-5,12.4-7.6,28.2-18.6,52.3-42.3,70.7-70.1Z"/>
|
||||
<path class="cls-2" d="M1424.8,301.6h25.4c-15.7-22.8-34.9-43-57.5-60.2-8.1-6.1-16.5-11.8-25.2-17v24.3c4.3,2.9,8.6,5.9,12.7,9.1,16.9,12.8,31.7,27.5,44.5,43.8Z"/>
|
||||
<polygon class="cls-2" points="1465.6 326.9 1447.3 326.9 1442.1 326.9 1369.8 326.9 1369.8 492.7 1382 492.7 1382 346.1 1429.6 346.1 1429.6 372 1429.6 492.7 1449.8 492.7 1452.9 492.7 1452.9 365.8 1452.9 347.6 1452.9 346.1 1460.7 346.1 1474.7 346.1 1501.9 346.1 1501.9 492.7 1531.1 492.7 1531.1 326.9 1470.8 326.9 1465.6 326.9"/>
|
||||
<path class="cls-2" d="M1601.3,369.4c11.5,0,20.9-9.4,20.9-20.9s-9.4-20.9-20.9-20.9-20.9,9.4-20.9,20.9,9.4,20.9,20.9,20.9Z"/>
|
||||
<circle class="cls-2" cx="1726.7" cy="348.5" r="20.9"/>
|
||||
<path class="cls-2" d="M1664,390.1c0-11.5-9.4-20.9-20.9-20.9-11.5,0-20.9,9.4-20.9,20.9,0,11.5,9.4,20.9,20.9,20.9,11.5,0,20.9-9.4,20.9-20.9Z"/>
|
||||
<circle class="cls-2" cx="1684.9" cy="390.1" r="20.9"/>
|
||||
<path class="cls-2" d="M1643.1,411c-11.5,0-20.9,9.4-20.9,20.9,0,11.5,9.4,20.9,20.9,20.9,11.5,0,20.9-9.4,20.9-20.9,0-11.5-9.4-20.9-20.9-20.9Z"/>
|
||||
<path class="cls-2" d="M1684.9,411c-11.5,0-20.9,9.4-20.9,20.9,0,11.5,9.4,20.9,20.9,20.9,11.5,0,20.9-9.4,20.9-20.9,0-11.5-9.4-20.9-20.9-20.9Z"/>
|
||||
<path class="cls-2" d="M1601.3,453c-11.5,0-20.9,9.4-20.9,20.9s9.4,20.9,20.9,20.9,20.9-9.4,20.9-20.9c0-11.5-9.4-20.9-20.9-20.9Z"/>
|
||||
<path class="cls-2" d="M1726.7,453c-11.5,0-20.9,9.4-20.9,20.9s9.4,20.9,20.9,20.9,20.9-9.4,20.9-20.9c0-11.5-9.4-20.9-20.9-20.9Z"/>
|
||||
<path class="cls-2" d="M1900.6,139.4h-125.5c-5.5,0-10,5-10,11.1v85c0,6.1,4.5,11.1,10,11.1h18.5c.1,0,.2,0,.3,0h0s58.9,0,58.9,0h0c0,0,.1.2.1.3v19.2c0,.1,0,.2,0,.2h-30.1s0,0,0-.2v-10h-3.7v10c0,2.2,1.7,3.9,3.8,3.9h30.1c2.1,0,3.8-1.8,3.8-3.9v-19.2c0-.1,0-.2,0-.3h25.1c.1,0,.2,0,.3,0h18.5c5.5,0,10-5,10-11.1v-85c0-6.1-4.5-11.1-10-11.1ZM1790.1,238.2v-59.5c0-2.5,1.7-4.6,3.8-4.6h87.8c2.1,0,3.8,2.1,3.8,4.6v59.5c0,2.5-1.6,4.5-3.7,4.6h-8c.3-.8.5-1.7.5-2.7v-41.6c0-3.5-2.6-6.4-5.8-6.4h-61.5c-3.2,0-5.8,2.9-5.8,6.4v41.6c0,1,.2,1.9.5,2.7h-8c-2,0-3.7-2.1-3.7-4.6ZM1822.7,242.4v-20.4c0-.2,0-.3,0-.4h30.1s.1.1.1.3v20.4c0,.2,0,.3,0,.3h0s-30.1,0-30.1,0h0s-.1-.2-.1-.4ZM1852.9,218h-30.1c-2.1,0-3.8,1.8-3.8,4.1v20.4c0,.1,0,.2,0,.4h-2.7c-.5,0-.9-.6-.9-1.3v-29.1c0-.7.4-1.3.9-1.3h43c.5,0,.9.6.9,1.3v29.1c0,.7-.4,1.3-.9,1.3h0s-2.7,0-2.7,0c0-.1,0-.2,0-.4v-20.4c0-2.2-1.7-4.1-3.8-4.1ZM1859.4,207.3h-43c-2.6,0-4.6,2.2-4.6,5v29.1c0,.5,0,.9.2,1.3h-4.8c-1.2,0-2.1-1.2-2.1-2.7v-41.6c0-1.5,1-2.6,2.1-2.6h61.5c1.2,0,2.1,1.2,2.1,2.6v41.6c0,1.5-1,2.6-2.1,2.6h0s-4.8,0-4.8,0c.1-.4.2-.9.2-1.3v-29.1c0-2.8-2.1-5-4.6-5ZM1906.9,235.4c0,4.1-2.8,7.3-6.3,7.3h-12.5c.8-1.3,1.3-2.9,1.3-4.6v-59.5c0-4.6-3.4-8.3-7.6-8.3h-87.8c-4.2,0-7.6,3.7-7.6,8.3v59.5c0,1.7.5,3.3,1.3,4.6h-12.5c-3.5,0-6.3-3.3-6.3-7.3v-85c0-4.1,2.8-7.3,6.3-7.3h125.5c3.5,0,6.3,3.3,6.3,7.3v85h0Z"/>
|
||||
<path class="cls-2" d="M2058,139h-126.3c-5.5,0-10,4.8-10,10.6v42.1h3.7v-42.1c0-3.8,2.8-6.9,6.3-6.9h126.3c3.5,0,6.3,3.1,6.3,6.9v80.9c0,3.3-2.8,7.1-6.3,7.1h-12.6c.8-1.3,1.2-2.8,1.2-4.4v-56.6c0-4.4-3.4-8-7.6-8h-88.4c-4.2,0-7.6,3.6-7.6,8v29.5h3.7v-29.5c0-2.4,1.7-4.3,3.9-4.3h88.4c2.1,0,3.9,1.9,3.9,4.3v56.6c0,2.4-1.8,4.4-3.9,4.4h0s-7.9,0-7.9,0c.3-.8.5-1.6.5-2.5v-39.6c0-3.4-2.6-6.1-5.9-6.1h-61.9c-3.2,0-5.9,2.8-5.9,6.1v20.6h3.7v-20.6c0-1.3,1-2.4,2.2-2.4h61.9c1.2,0,2.2,1.1,2.2,2.4v39.6c0,1.3-.9,2.4-1.9,2.5h-5c0-.4.2-.8.2-1.2v-27.7c0-2.7-2.1-4.9-4.7-4.9h-43.3c-2.6,0-4.7,2.2-4.7,4.9v14.4h3.7v-14.4c0-.6.4-1.1,1-1.1h43.3c.5,0,.9.5.9,1.1v27.7c0,.6-.4,1.2-.9,1.2h0s-2.7,0-2.7,0c0-.1,0-.2,0-.3v-19.4c0-2.2-1.7-4-3.8-4h-30.3c-2.1,0-3.8,1.8-3.8,4v10.1h3.7v-10.1c0-.2,0-.2.1-.2h30.3s.1,0,.1.2v19.4c0,.2,0,.3-.1.3h0s-30.1,0-30.1,0v3.7s45.9,0,45.9,0h0c.2,0,.4,0,.6,0h31.6c5.4,0,10-5,10-10.8v-80.9c0-5.9-4.5-10.6-10-10.6Z"/>
|
||||
<path class="cls-2" d="M2010.1,261c0,.2,0,.2-.1.2h-30.3s-.1,0-.1-.2v-19.4c0-.2,0-.3.1-.3v-3.7c-2.1,0-3.8,1.8-3.8,4v19.4c0,2.2,1.7,4,3.8,4h30.3c2.1,0,3.8-1.8,3.8-4v-10.1h-3.7v10.1Z"/>
|
||||
<path class="cls-2" d="M2225,336.8h-3.7v-186c0-3.8-2.8-6.9-6.2-6.9h-124.4c-3.4,0-6.2,3.1-6.2,6.9h-3.7c0-5.8,4.5-10.6,9.9-10.6h124.4c5.5,0,9.9,4.7,9.9,10.6v186Z"/>
|
||||
<path class="cls-2" d="M2203.9,307.6h-3.7v-130.2c0-2.3-1.7-4.3-3.8-4.3h-87.1c-2.1,0-3.8,1.9-3.8,4.3h-3.7c0-4.4,3.4-8,7.5-8h87.1c4.1,0,7.5,3.6,7.5,8v130.2Z"/>
|
||||
<path class="cls-2" d="M2189.2,287.2h-3.7v-91.2c0-1.3-.9-2.4-2.1-2.4h-61c-1.2,0-2.1,1.1-2.1,2.4h-3.7c0-3.4,2.6-6.1,5.8-6.1h61c3.2,0,5.8,2.8,5.8,6.1v91.2h0Z"/>
|
||||
<path class="cls-2" d="M2178.8,272.8h-3.7v-63.8c0-.6-.4-1.1-.9-1.1h-42.7c-.5,0-.9.5-.9,1.1h-3.7c0-2.7,2.1-4.9,4.6-4.9h42.7c2.6,0,4.6,2.2,4.6,4.9v63.8h0Z"/>
|
||||
<path class="cls-2" d="M2171.6,262.8h-3.7v-44.7c0-.2,0-.2,0-.2h-29.9s0,0,0,.2h-3.7c0-2.2,1.7-4,3.8-4h29.9c2.1,0,3.8,1.8,3.8,4v44.7Z"/>
|
||||
<path class="cls-2" d="M2137.8,265.9c-2.1,0-3.8-1.8-3.8-4h3.7c0,.2,0,.3.1.3v3.7Z"/>
|
||||
<path class="cls-2" d="M2131.5,276.2c-2.5,0-4.6-2.2-4.6-4.9h3.7c0,.6.4,1.2.9,1.2v3.7h0Z"/>
|
||||
<path class="cls-2" d="M2122.3,291.6c-3.2,0-5.8-2.8-5.8-6.3h3.7c0,1.4.9,2.5,2.1,2.5v3.7h0Z"/>
|
||||
<path class="cls-2" d="M2109.4,312.3c-4.1,0-7.5-3.7-7.5-8.1h3.7c0,2.4,1.7,4.4,3.7,4.4v3.7h0Z"/>
|
||||
<path class="cls-2" d="M2090.4,344.1c-5.5,0-9.9-4.9-9.9-10.9h3.7c0,4,2.8,7.2,6.2,7.2v3.7Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M1157.8,206.6v9.8h-55.5v-78.5h9.8v68.7h45.7Z"/>
|
||||
<path class="cls-2" d="M1005.1,193.6c0,14.2-10.2,23.1-24.8,22.8h-4.8c0-.1-34.1-.1-34.1-.1v-78.3h37.5c14.6.6,21,9.8,21,20.7s-2.1,11.3-5,14.5c5.8,3.9,10.1,11.2,10.1,20.4ZM951.2,169.7h27.9c7.9,0,11.1-4.6,11.1-10.9s-3-11.1-13-11.1h-25.9v22h0ZM995.3,192.8c0-7.3-3.9-13.2-13.4-13.2h-30.8v26.9h29.6c10.6,0,14.5-5.6,14.5-13.7Z"/>
|
||||
<circle class="cls-2" cx="1021.5" cy="147.7" r="9.9" transform="translate(643.1 1108) rotate(-76.7)"/>
|
||||
<circle class="cls-2" cx="1080.7" cy="147.7" r="9.9"/>
|
||||
<circle class="cls-2" cx="1041.3" cy="167.3" r="9.9"/>
|
||||
<path class="cls-2" d="M1070.8,167.3c0,5.4-4.4,9.9-9.8,9.9-5.4,0-9.9-4.4-9.9-9.8,0-5.4,4.4-9.9,9.8-9.9,5.4,0,9.9,4.4,9.9,9.8Z"/>
|
||||
<circle class="cls-2" cx="1041.3" cy="187" r="9.9"/>
|
||||
<circle class="cls-2" cx="1061" cy="187" r="9.9"/>
|
||||
<circle class="cls-2" cx="1021.5" cy="206.8" r="9.9" transform="translate(585.5 1153.5) rotate(-76.7)"/>
|
||||
<circle class="cls-2" cx="1080.7" cy="206.8" r="9.9"/>
|
||||
<path class="cls-2" d="M574.6,189.6h-35.7l-10.5,25.9h-10.4l31.4-78.3h14.4l31.4,78.3h-10.4l-10.4-25.9ZM570.7,179.8l-12.2-30.4-1.7-4.4-1.7,4.4-12.2,30.4h27.9Z"/>
|
||||
<path class="cls-2" d="M740.7,137.3c13.2.1,22,8.6,22,21.3s-8.4,23.1-21,23.1h-32.2v33.8h-10.1v-78.3h41.3ZM709.5,172h32.1c7.1-.1,11.6-5.2,11.6-12.8s-4.5-12-11.6-12.1h-32.1v24.9Z"/>
|
||||
<path class="cls-2" d="M832.9,192.5c-.1,16-11.1,25.4-29.5,25.4s-31.1-9.8-31.1-24.4v-56.2h10.2v54.9c.1,10.1,7.6,15.7,20.3,15.7s19.7-5.5,19.8-15.7v-54.9h10.2v55.1Z"/>
|
||||
<path class="cls-2" d="M875.9,208.1c9.6,0,16.6-3.7,16.6-11.5s-5.5-10.1-10.5-11.9c-6.3-2.2-11.5-3-18.9-5.2-9.2-2.8-16-10.6-16-21.5s8.4-22.9,25.7-22.9,20.5,2.2,29.6,12.1l-6.9,7c-8.2-7.9-18.4-9.3-23.3-9.3-9.3,0-15,6-15,12.7s3.2,10.4,10.8,12.5c3.9,1.2,10,2.9,18.5,5.4,12.7,3.8,16,11.5,16,20.8s-10.7,21.5-26.7,21.5-23.3-4.1-30.2-13.9l8.4-6.2c5.8,7.5,13.1,10.4,21.8,10.4Z"/>
|
||||
<polygon class="cls-2" points="512.3 215.6 468.8 215.6 468.8 196.8 448.4 196.8 448.4 150.4 469.1 150.4 469.1 136.9 492.3 136.9 492.3 146 478.2 146 478.2 159.5 457.6 159.5 457.6 187.6 477.9 187.6 477.9 206.4 512.3 206.4 512.3 215.6"/>
|
||||
<path class="cls-2" d="M73.2,137.2c13.2.1,22,8.6,22,21.3s-8.4,23.1-21,23.1h-15.3l31.8,33.9h-13.6l-30.6-33.9h-6.8v33.8h-10.1v-78.3h43.6ZM39.7,171.9h34.4c7.1-.1,11.6-5.2,11.6-12.8s-4.5-12-11.6-12.1h-34.4v24.9Z"/>
|
||||
<path class="cls-2" d="M156.4,189.6h-35.7l-10.5,25.9h-10.4l31.4-78.3h14.4l31.4,78.3h-10.4l-10.4-25.9ZM152.5,179.8l-12.2-30.4-1.7-4.4-1.7,4.4-12.2,30.4h27.9Z"/>
|
||||
<path class="cls-2" d="M220.7,137.2c19.8.1,32.3,16.2,32.3,38.1s-10.2,40.2-33,40.2h-32.6v-78.3h33.3ZM197.5,205.7h24.5c13.7,0,21.2-11.9,21.2-29.8s-8.6-28.8-21.6-28.9h-24.1v58.7Z"/>
|
||||
<path class="cls-2" d="M310.7,205.9v9.6h-50.6v-9.6h20.7v-59.2h-17.3v-9.6h43.7v9.6h-16.9v59.2h20.4Z"/>
|
||||
<polygon class="cls-2" points="612 215.7 612 146.2 634.5 146.2 634.5 215.7 645.5 215.7 645.5 146.2 668.6 146.2 668.6 215.7 682.4 215.7 682.4 136.7 606.3 136.7 606.3 215.7 612 215.7"/>
|
||||
<path class="cls-2" d="M319.6,162.4v11.4c15.8-9.2,29.1-21.6,39.1-36.5h-11.9c-7.5,9.8-16.6,18.2-27.1,25.1Z"/>
|
||||
<path class="cls-2" d="M403,191v-11.7c-2,1.3-4,2.6-5.9,3.9-13.1,9.2-24.2,20.2-32.6,32.4h12.1c7.4-9.3,16.4-17.6,26.5-24.7Z"/>
|
||||
<path class="cls-2" d="M319.6,190.6c2,1.4,4,2.8,6,4.3,8,6.1,15,13,21,20.7h12c-7.4-10.8-16.5-20.3-27.1-28.4-3.8-2.9-7.8-5.6-11.9-8v11.4h0Z"/>
|
||||
<path class="cls-2" d="M403,161.9c-1.5-1-2.9-2-4.3-3-8.5-6.3-15.9-13.5-22.2-21.5h-12c7.6,11.1,17.1,21,28.4,29.3,3.2,2.4,6.6,4.6,10.1,6.7v-11.5h0Z"/>
|
||||
</g>
|
||||
<g id="small">
|
||||
<g>
|
||||
<path class="cls-2" d="M605.8,679.6h-35.7l-10.5,25.9h-10.4l31.4-78.3h14.4l31.4,78.3h-10.4l-10.4-25.9ZM601.8,669.8l-12.2-30.4-1.7-4.4-1.7,4.4-12.2,30.4h27.9Z"/>
|
||||
<path class="cls-2" d="M771.9,627.3c13.2.1,22,8.6,22,21.3s-8.4,23.1-21,23.1h-32.2v33.8h-10.1v-78.3h41.3ZM740.7,662h32.1c7.1-.1,11.6-5.2,11.6-12.8s-4.5-12-11.6-12.1h-32.1v24.9Z"/>
|
||||
<path class="cls-2" d="M864.1,682.5c-.1,16-11.1,25.4-29.5,25.4s-31.1-9.8-31.1-24.4v-56.2h10.2v54.9c.1,10.1,7.6,15.7,20.3,15.7s19.7-5.5,19.8-15.7v-54.9h10.2v55.1Z"/>
|
||||
<path class="cls-2" d="M907,698.1c9.6,0,16.6-3.7,16.6-11.5s-5.5-10.1-10.5-11.9c-6.3-2.2-11.5-3-18.9-5.2-9.2-2.8-16-10.6-16-21.5s8.4-22.9,25.7-22.9,20.5,2.2,29.6,12.1l-6.9,7c-8.2-7.9-18.4-9.3-23.3-9.3-9.3,0-15,6-15,12.7s3.2,10.4,10.8,12.5c3.9,1.2,10,2.9,18.5,5.4,12.7,3.8,16,11.5,16,20.8s-10.7,21.5-26.7,21.5-23.3-4.1-30.2-13.9l8.4-6.2c5.8,7.5,13.1,10.4,21.8,10.4Z"/>
|
||||
<polygon class="cls-2" points="543.4 705.6 500 705.6 500 686.8 479.6 686.8 479.6 640.4 500.3 640.4 500.3 626.9 523.5 626.9 523.5 636 509.4 636 509.4 649.5 488.7 649.5 488.7 677.6 509.1 677.6 509.1 696.4 543.4 696.4 543.4 705.6"/>
|
||||
<path class="cls-2" d="M104.4,627.2c13.2.1,22,8.6,22,21.3s-8.4,23.1-21,23.1h-15.3l31.8,33.9h-13.6l-30.6-33.9h-6.8v33.8h-10.1v-78.3h43.6ZM70.9,661.9h34.4c7.1-.1,11.6-5.2,11.6-12.8s-4.5-12-11.6-12.1h-34.4v24.9Z"/>
|
||||
<path class="cls-2" d="M187.6,679.6h-35.7l-10.5,25.9h-10.4l31.4-78.3h14.4l31.4,78.3h-10.4l-10.4-25.9ZM183.7,669.8l-12.2-30.4-1.7-4.4-1.7,4.4-12.2,30.4h27.9Z"/>
|
||||
<path class="cls-2" d="M251.8,627.2c19.8.1,32.3,16.2,32.3,38.1s-10.2,40.2-33,40.2h-32.6v-78.3h33.3ZM228.7,695.7h24.5c13.7,0,21.2-11.9,21.2-29.8s-8.6-28.8-21.6-28.9h-24.1v58.7h0Z"/>
|
||||
<path class="cls-2" d="M341.9,695.9v9.6h-50.6v-9.6h20.7v-59.2h-17.3v-9.6h43.7v9.6h-16.9v59.2h20.4Z"/>
|
||||
<polygon class="cls-2" points="643.2 705.7 643.2 636.2 665.7 636.2 665.7 705.7 676.7 705.7 676.7 636.2 699.8 636.2 699.8 705.7 713.6 705.7 713.6 626.7 637.4 626.7 637.4 705.7 643.2 705.7"/>
|
||||
<path class="cls-2" d="M350.8,652.4v11.4c15.8-9.2,29.1-21.6,39.1-36.5h-11.9c-7.5,9.8-16.6,18.2-27.1,25.1Z"/>
|
||||
<path class="cls-2" d="M434.1,681v-11.7c-2,1.3-4,2.6-5.9,3.9-13.1,9.2-24.2,20.2-32.6,32.4h12.1c7.4-9.3,16.4-17.6,26.5-24.7Z"/>
|
||||
<path class="cls-2" d="M350.8,680.6c2,1.4,4,2.8,6,4.3,8,6,15,13,21,20.7h12c-7.4-10.8-16.5-20.3-27.1-28.4-3.8-2.9-7.8-5.6-11.9-8v11.5h0Z"/>
|
||||
<path class="cls-2" d="M434.1,651.9c-1.5-1-2.9-2-4.3-3-8.5-6.3-15.9-13.5-22.2-21.5h-12c7.6,11.1,17.1,21,28.4,29.3,3.2,2.4,6.6,4.6,10.1,6.7v-11.5Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M963.9,677.9c0,5.5-4.4,10-9.9,10-5.5,0-10-4.4-10-9.9,0-5.5,4.4-10,9.9-10,5.5,0,10,4.4,10,9.9Z"/>
|
||||
<circle class="cls-2" cx="973.8" cy="677.8" r="10"/>
|
||||
<circle class="cls-2" cx="954" cy="697.8" r="10"/>
|
||||
<circle class="cls-2" cx="973.9" cy="697.8" r="10"/>
|
||||
<path class="cls-2" d="M1058.4,683.5c0,14.2-10.2,23.1-24.8,22.8h-4.8c0-.1-34.1-.1-34.1-.1v-78.3h37.5c14.6.6,21,9.8,21,20.7s-2.1,11.3-5,14.5c5.8,3.9,10.1,11.2,10.1,20.4ZM1004.5,659.7h27.9c7.9,0,11.1-4.6,11.1-10.9s-3-11.1-13-11.1h-25.9v22h0ZM1048.6,682.7c0-7.3-3.9-13.2-13.4-13.2h-30.7v26.9h29.6c10.6,0,14.5-5.6,14.5-13.7Z"/>
|
||||
<path class="cls-2" d="M1077.9,637.7v22h41.6v9.8h-41.6v26.9h46.2v9.8h-55.9v-78.3h55.9v9.8h-46.2Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 33 KiB |
|
@ -1,69 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1190.6 841.9">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #000;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<path class="cls-1" d="M80,318.5c14.5.1,24.2,9.5,24.2,23.4s-9.2,25.4-23,25.4h-16.8l34.9,37.2h-14.9l-33.6-37.2h-7.4v37.2h-11.1v-86h47.9,0ZM43.2,356.6h37.8c7.9-.1,12.8-5.7,12.8-14.1s-4.9-13.2-12.8-13.3h-37.8v27.3h0Z"/>
|
||||
<path class="cls-1" d="M171.4,376.1h-39.2l-11.5,28.4h-11.4l34.5-86h15.8l34.5,86h-11.4l-11.4-28.4h.1ZM167.1,365.4l-13.4-33.4-1.9-4.8-1.9,4.8-13.4,33.4h30.6,0Z"/>
|
||||
<path class="cls-1" d="M242,318.5c21.8.1,35.5,17.9,35.5,41.9s-11.2,44.2-36.3,44.2h-35.8v-86h36.6ZM216.6,393.8h27c15.1,0,23.3-13,23.3-32.7s-9.5-31.6-23.8-31.7h-26.4v64.5h0Z"/>
|
||||
<path class="cls-1" d="M341,394v10.5h-55.6v-10.5h22.8v-65h-19v-10.5h48.1v10.5h-18.6v65h22.4,0Z"/>
|
||||
<path class="cls-1" d="M170.9,479.3h-39.2l-11.5,28.4h-11.4l34.5-86h15.8l34.5,86h-11.4l-11.4-28.4h.1ZM166.5,468.6l-13.4-33.4-1.9-4.8-1.9,4.8-13.4,33.4h30.6,0Z"/>
|
||||
<path class="cls-1" d="M353.4,421.9c14.5.1,24.2,9.5,24.2,23.4s-9.2,25.4-23,25.4h-35.4v37.2h-11.1v-86h45.4,0ZM319.1,460h35.3c7.9-.1,12.8-5.7,12.8-14.1s-4.9-13.1-12.8-13.3h-35.3v27.3h0Z"/>
|
||||
<path class="cls-1" d="M454.7,482.5c-.1,17.6-12.1,28-32.4,28s-34.2-10.8-34.2-26.8v-61.7h11.2v60.4c.1,11.1,8.3,17.2,22.3,17.2s21.6-6.1,21.8-17.2v-60.4h11.2v60.6h0Z"/>
|
||||
<path class="cls-1" d="M501.9,499.7c10.5,0,18.2-4,18.2-12.7s-6.1-11.1-11.5-13c-7-2.4-12.6-3.3-20.7-5.7-10.1-3-17.6-11.6-17.6-23.6s9.2-25.2,28.2-25.2,22.5,2.4,32.5,13.3l-7.6,7.7c-9-8.7-20.2-10.2-25.5-10.2-10.2,0-16.4,6.6-16.4,13.9s3.5,11.4,11.9,13.8c4.3,1.2,11,3.2,20.3,6,13.9,4.2,17.6,12.7,17.6,22.9s-11.8,23.6-29.3,23.6-25.5-4.6-33.2-15.3l9.2-6.9c6.3,8.2,14.4,11.4,23.9,11.4h0Z"/>
|
||||
<polygon class="cls-1" points="205.7 421.8 205.7 507.6 212 507.6 212 431.7 236.6 431.7 236.6 507.6 248.7 507.6 248.7 431.7 274.1 431.7 274.1 507.6 289.3 507.6 289.3 421.8 205.7 421.8"/>
|
||||
<polygon class="cls-1" points="102.3 507.9 54.6 507.9 54.6 487.3 32.2 487.3 32.2 436.2 54.9 436.2 54.9 421.5 80.4 421.5 80.4 431.5 65 431.5 65 446.3 42.3 446.3 42.3 477.2 64.6 477.2 64.6 497.8 102.3 497.8 102.3 507.9"/>
|
||||
<path class="cls-1" d="M435.7,369.1c-14.4,10-26.5,22.2-35.8,35.5h13.3c8.1-10.2,18-19.3,29-27v-12.8c-2.2,1.4-4.4,2.8-6.6,4.4h0Z"/>
|
||||
<path class="cls-1" d="M431.2,350.9c3.6,2.7,7.3,5.1,11.1,7.4v-12.6c-1.7-1.1-3.3-2.2-4.8-3.4-9.5-7-17.6-15-24.5-23.8h-13.1c8.3,12.4,18.9,23.2,31.3,32.4h0Z"/>
|
||||
<path class="cls-1" d="M393.8,318.5h-13.1c-8.2,10.9-18.4,20.2-30,27.8v12.5c17.4-10.1,32.2-23.8,43.1-40.3Z"/>
|
||||
<path class="cls-1" d="M363.7,373.4c-4.2-3.2-8.6-6.1-13.1-8.8v12.6c2.2,1.5,4.4,3.1,6.6,4.7,8.7,6.7,16.4,14.2,23.1,22.7h13.2c-8.1-11.8-18.1-22.3-29.8-31.2h0Z"/>
|
||||
<g>
|
||||
<path class="cls-1" d="M56.7,579.5c0,6-4.8,10.9-10.9,10.9s-10.9-4.8-10.9-10.9,4.9-10.9,10.9-10.9,10.9,4.9,10.9,10.9Z"/>
|
||||
<path class="cls-1" d="M78.5,579.5c0,6-4.8,10.9-10.9,10.9s-10.9-4.8-10.9-10.9,4.9-10.9,10.9-10.9,10.9,4.8,10.9,10.9Z"/>
|
||||
<path class="cls-1" d="M56.7,601.4c0,6-4.9,10.9-10.9,10.9s-10.9-4.8-10.9-10.9,4.8-10.9,10.9-10.9,10.9,4.8,10.9,10.9Z"/>
|
||||
<circle class="cls-1" cx="67.6" cy="601.4" r="10.9"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-1" d="M160.5,585.8c0,15.5-11.2,25.4-27.2,25.1h-5.3c0-.1-37.4-.1-37.4-.1v-86h41.3c16.1.7,23,10.8,23,22.8s-2.3,12.4-5.4,16c6.3,4.3,11.1,12.3,11.1,22.4h0ZM101.2,559.6h30.6c8.7,0,12.1-5.1,12.1-12s-3.3-12.1-14.3-12.1h-28.4v24.2h0ZM149.7,584.9c0-8-4.3-14.5-14.7-14.5h-33.8v29.6h32.5c11.6,0,16-6.2,16-15.1h0Z"/>
|
||||
<path class="cls-1" d="M182,535.4v24.2h45.6v10.8h-45.6v29.6h50.7v10.8h-61.5v-86h61.5v10.8h-50.7Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-1" d="M402,556.2c0,41.1-10,57.5-31.4,57.5s-23.3-6.1-28.2-15.2l8.3-6c3.9,7.2,10.1,10.6,20.4,10.6s19.1-7.3,20.4-26.2c-5.3,4.7-12.5,7.7-20.5,7.7-18.7,0-30.9-14.2-30.9-28.5s12.3-33.6,30.9-33.6,31,14.3,31,33.6h0ZM392.5,554.3c0-13.7-10.4-20.3-21.4-20.3s-20.5,6.6-20.5,20.3,8.7,19.7,20.5,19.7,21.4-8,21.4-19.7Z"/>
|
||||
<path class="cls-1" d="M470.4,600.2v10.9h-54.7v-10.9c33.9-25.2,42.4-35.9,42.4-49.5s-6.7-17.1-18.7-17.1-19.5,6.7-15.8,23.2l-10.1,4.7c-5.4-17.9,2.8-39,27-39s28,10.8,28,26-12.4,35.2-37.2,51.8h39.3Z"/>
|
||||
<path class="cls-1" d="M489.4,598.1v13h-13v-13h13Z"/>
|
||||
<path class="cls-1" d="M520.6,525.1h10.9v86.2h-10.9v-72.5l-23.1,22.4v-13.4l23.1-22.6Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-1" d="M1157.8,206.6v9.8h-55.5v-78.5h9.8v68.7h45.7Z"/>
|
||||
<path class="cls-1" d="M1005.1,193.6c0,14.2-10.2,23.1-24.8,22.8h-4.8c0-.1-34.1-.1-34.1-.1v-78.3h37.5c14.6.6,21,9.8,21,20.7s-2.1,11.3-5,14.5c5.8,3.9,10.1,11.2,10.1,20.4h0ZM951.2,169.7h27.9c7.9,0,11.1-4.6,11.1-10.9s-3-11.1-13-11.1h-25.9v22h0ZM995.3,192.8c0-7.3-3.9-13.2-13.4-13.2h-30.8v26.9h29.6c10.6,0,14.5-5.6,14.5-13.7h.1Z"/>
|
||||
<circle class="cls-1" cx="1021.8" cy="147.9" r="9.9" transform="translate(642.9 1108.3) rotate(-76.7)"/>
|
||||
<circle class="cls-1" cx="1080.7" cy="147.7" r="9.9"/>
|
||||
<circle class="cls-1" cx="1041.3" cy="167.3" r="9.9"/>
|
||||
<path class="cls-1" d="M1070.8,167.3c0,5.4-4.4,9.9-9.8,9.9s-9.9-4.4-9.9-9.8,4.4-9.9,9.8-9.9,9.9,4.4,9.9,9.8Z"/>
|
||||
<circle class="cls-1" cx="1041.3" cy="187" r="9.9"/>
|
||||
<circle class="cls-1" cx="1061" cy="187" r="9.9"/>
|
||||
<circle class="cls-1" cx="1021.7" cy="207" r="9.9" transform="translate(585.3 1153.7) rotate(-76.7)"/>
|
||||
<circle class="cls-1" cx="1080.7" cy="206.8" r="9.9"/>
|
||||
<path class="cls-1" d="M574.6,189.6h-35.7l-10.5,25.9h-10.4l31.4-78.3h14.4l31.4,78.3h-10.4l-10.4-25.9h.2ZM570.7,179.8l-12.2-30.4-1.7-4.4-1.7,4.4-12.2,30.4h27.9,0Z"/>
|
||||
<path class="cls-1" d="M740.7,137.3c13.2.1,22,8.6,22,21.3s-8.4,23.1-21,23.1h-32.2v33.8h-10.1v-78.3h41.3ZM709.5,172h32.1c7.1-.1,11.6-5.2,11.6-12.8s-4.5-12-11.6-12.1h-32.1v24.9h0Z"/>
|
||||
<path class="cls-1" d="M832.9,192.5c0,16-11.1,25.4-29.5,25.4s-31.1-9.8-31.1-24.4v-56.2h10.2v54.9c0,10.1,7.6,15.7,20.3,15.7s19.7-5.5,19.8-15.7v-54.9h10.2v55.1h0Z"/>
|
||||
<path class="cls-1" d="M875.9,208.1c9.6,0,16.6-3.7,16.6-11.5s-5.5-10.1-10.5-11.9c-6.3-2.2-11.5-3-18.9-5.2-9.2-2.8-16-10.6-16-21.5s8.4-22.9,25.7-22.9,20.5,2.2,29.6,12.1l-6.9,7c-8.2-7.9-18.4-9.3-23.3-9.3-9.3,0-15,6-15,12.7s3.2,10.4,10.8,12.5c3.9,1.2,10,2.9,18.5,5.4,12.7,3.8,16,11.5,16,20.8s-10.7,21.5-26.7,21.5-23.3-4.1-30.2-13.9l8.4-6.2c5.8,7.5,13.1,10.4,21.8,10.4h.1Z"/>
|
||||
<polygon class="cls-1" points="512.3 215.6 468.8 215.6 468.8 196.8 448.4 196.8 448.4 150.4 469.1 150.4 469.1 136.9 492.3 136.9 492.3 146 478.2 146 478.2 159.5 457.6 159.5 457.6 187.6 477.9 187.6 477.9 206.4 512.3 206.4 512.3 215.6"/>
|
||||
<path class="cls-1" d="M73.2,137.2c13.2.1,22,8.6,22,21.3s-8.4,23.1-21,23.1h-15.3l31.8,33.9h-13.6l-30.6-33.9h-6.8v33.8h-10.1v-78.3h43.6ZM39.7,171.9h34.4c7.1-.1,11.6-5.2,11.6-12.8s-4.5-12-11.6-12.1h-34.4v24.9h0Z"/>
|
||||
<path class="cls-1" d="M156.4,189.6h-35.7l-10.5,25.9h-10.4l31.4-78.3h14.4l31.4,78.3h-10.4l-10.4-25.9h.2ZM152.5,179.8l-12.2-30.4-1.7-4.4-1.7,4.4-12.2,30.4h27.9-.1Z"/>
|
||||
<path class="cls-1" d="M220.7,137.2c19.8.1,32.3,16.2,32.3,38.1s-10.2,40.2-33,40.2h-32.6v-78.3h33.3ZM197.5,205.7h24.5c13.7,0,21.2-11.9,21.2-29.8s-8.6-28.8-21.6-28.9h-24.1v58.7h0Z"/>
|
||||
<path class="cls-1" d="M310.7,205.9v9.6h-50.6v-9.6h20.7v-59.2h-17.3v-9.6h43.7v9.6h-16.9v59.2h20.4,0Z"/>
|
||||
<polygon class="cls-1" points="612 215.7 612 146.2 634.5 146.2 634.5 215.7 645.5 215.7 645.5 146.2 668.6 146.2 668.6 215.7 682.4 215.7 682.4 136.7 606.3 136.7 606.3 215.7 612 215.7"/>
|
||||
<path class="cls-1" d="M319.6,162.4v11.4c15.8-9.2,29.1-21.6,39.1-36.5h-11.9c-7.5,9.8-16.6,18.2-27.1,25.1h-.1Z"/>
|
||||
<path class="cls-1" d="M403,191v-11.7c-2,1.3-4,2.6-5.9,3.9-13.1,9.2-24.2,20.2-32.6,32.4h12.1c7.4-9.3,16.4-17.6,26.5-24.7h-.1Z"/>
|
||||
<path class="cls-1" d="M319.6,190.6c2,1.4,4,2.8,6,4.3,8,6.1,15,13,21,20.7h12c-7.4-10.8-16.5-20.3-27.1-28.4-3.8-2.9-7.8-5.6-11.9-8v11.4h0Z"/>
|
||||
<path class="cls-1" d="M403,161.9c-1.5-1-2.9-2-4.3-3-8.5-6.3-15.9-13.5-22.2-21.5h-12c7.6,11.1,17.1,21,28.4,29.3,3.2,2.4,6.6,4.6,10.1,6.7v-11.5h0Z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.9 KiB |
|
@ -1,333 +0,0 @@
|
|||
:root {
|
||||
--a-player-bar-bg: #738EF2;
|
||||
--a-player-bar-fg: #4897c7;
|
||||
--a-player-url-fg: white;
|
||||
--a-playlist-header-bg: #F6ED80;;
|
||||
--a-playlist-header-fg: #222;
|
||||
--a-player-panel-bg: #738ef2;
|
||||
--a-player-panel-fg: white;
|
||||
--a-sound-hv-bg: #f6ed80;
|
||||
--a-sound-hv-fg: #444;
|
||||
--a-sound-bg: #f6ed80;
|
||||
--body-bg: unset;
|
||||
--break-color: transparent;
|
||||
--button-bg: #F4F88D;
|
||||
--button-fg: #222;
|
||||
--button-hv-bg: #F4F88D;
|
||||
--button-hv-fg: #1d3cab;
|
||||
--button-active-fg: white;
|
||||
--button-active-bg: #738ef2;
|
||||
--heading-font-family: "campus_grotesk";
|
||||
--heading-link-hv-fg: #aa217b;
|
||||
--heading-hg-fg: #fff;
|
||||
--link-fg: #3b47ff;
|
||||
--link-hv-fg: #c40c85;
|
||||
--main-color-light: #F4F881;
|
||||
--nav-bg: transparent;
|
||||
--nav-fg: #222;
|
||||
--nav-secondary-bg: transparent;
|
||||
--nav-hv-bg: unset;
|
||||
--nav-active-bg: unset;
|
||||
--nav-active-fg: white;
|
||||
--text-color: #75124e;
|
||||
--text-color-light: #bbb;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "campus_grotesk";
|
||||
src:
|
||||
local("campus_grotesk"),
|
||||
url("/static/radiocampus/fonts/campus_grotesk/CampusGroteskv24-Regular.woff2") format("woff2"),
|
||||
url("/static/radiocampus/fonts/campus_grotesk/CampusGroteskv24-Regular.woff") format("woff"),
|
||||
url("/static/radiocampus/fonts/campus_grotesk/CampusGroteskv24-Regular.otf") format("opentype") tech(color-COLRv1);
|
||||
}
|
||||
|
||||
body {
|
||||
color: #222;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
|
||||
body.home, body.home .preview .headings a, body.home .page a {
|
||||
color: #fff;
|
||||
}
|
||||
body.home .preview .headings a:hover {
|
||||
color: #f4f88d !important;
|
||||
}
|
||||
body.home .nav.primary {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
body.yellow {
|
||||
background: url(/static/radiocampus/backgrounds/degrade-jaune.jpg) repeat-x top fixed;
|
||||
}
|
||||
body.yellow.home {
|
||||
background: url(/static/radiocampus/backgrounds/photo-degrade-02.jpg) no-repeat center/cover;
|
||||
}
|
||||
body.blue {
|
||||
background: url(/static/radiocampus/backgrounds/degrade-bleu.jpg) repeat-x top fixed;
|
||||
}
|
||||
body.blue.home {
|
||||
background: url(/static/radiocampus/backgrounds/photo-degrade-01.jpg) no-repeat center/cover;
|
||||
}
|
||||
body.blue #grandlogo img {
|
||||
content: url('/static/radiocampus/logos/logo-RC-blanc1.png');
|
||||
}
|
||||
body.yellow #grandlogo img {
|
||||
content: url('/static/radiocampus/logos/logo-RC-bleu1.png');
|
||||
}
|
||||
body.blue.home #grandlogo img {
|
||||
content: url('/static/radiocampus/logos/logo-RC-blanc2.png');
|
||||
}
|
||||
body.yellow.home #grandlogo img {
|
||||
content: url('/static/radiocampus/logos/logo-RC-bleu2.png');
|
||||
}
|
||||
body.yellow .nav .nav-item.active {
|
||||
color: #7E6B64 !important;
|
||||
}
|
||||
body.blue #grandlogo img, body.yellow #grandlogo img {
|
||||
width: 120px;
|
||||
margin: 12px 0 0 48px;
|
||||
}
|
||||
body.blue.home #grandlogo, body.yellow.home #grandlogo {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
body.blue.home #grandlogo img , body.yellow.home #grandlogo img {
|
||||
margin: 12px auto 0 auto;
|
||||
width: 960px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
.a-player-bar {
|
||||
border-top: 1px solid #555;
|
||||
}
|
||||
.a-player .button, .a-player-bar-content {
|
||||
color: white;
|
||||
}
|
||||
#player .a-sound-item .label {
|
||||
color: white !important;
|
||||
}
|
||||
.a-switch-nav span {
|
||||
color: white;
|
||||
}
|
||||
.a-switch-nav span:hover {
|
||||
color: #333;
|
||||
}
|
||||
.button, a.button, button.button {
|
||||
border: 0;
|
||||
}
|
||||
.nav.primary .nav-brand {
|
||||
display: none;
|
||||
}
|
||||
.nav.secondary .nav-item {
|
||||
color: #7E6B64 !important;
|
||||
}
|
||||
.nav-item:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.nav.primary .nav-item {
|
||||
font-weight: unset;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.page section.container {
|
||||
margin-top: 0;
|
||||
padding-top: 0.6rem;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.page {
|
||||
margin-top: 0;
|
||||
padding-top: var(--nav-primary-height);
|
||||
}
|
||||
.nav.primary .nav-brand {
|
||||
display: inline-block;
|
||||
}
|
||||
.nav.secondary .nav-item {
|
||||
color: white !important;
|
||||
}
|
||||
.dropdown-trigger .icon, .icon.bullet {
|
||||
color: white;
|
||||
}
|
||||
.dropdown.is-right .dropdown-menu {
|
||||
left: 0;
|
||||
}
|
||||
#grandlogo {
|
||||
display: none;
|
||||
}
|
||||
.navs, .nav-menu.active {
|
||||
background-color: #7892f1; /*#738ef2;*/
|
||||
}
|
||||
.nav .nav-item {
|
||||
color: white !important;
|
||||
}
|
||||
body.yellow .nav .nav-item.active {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* yellow theme is not implemented for small screens */
|
||||
body.yellow {
|
||||
background: url(/static/radiocampus/backgrounds/degrade-bleu.jpg) repeat-x top/auto 520px;
|
||||
}
|
||||
body.yellow.home {
|
||||
background: url(/static/radiocampus/backgrounds/photo-degrade-01.jpg) no-repeat center/cover;
|
||||
}
|
||||
|
||||
.navs .nav + .nav {
|
||||
flex-grow: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1408px) {
|
||||
.container:not(.is-max-desktop):not(.is-max-widescreen) {
|
||||
max-width: unset;
|
||||
margin: 10px 64px;
|
||||
}
|
||||
body.home .container:not(.is-max-desktop):not(.is-max-widescreen) {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1216px) {
|
||||
.container:not(.is-max-desktop):not(.is-max-widescreen) {
|
||||
max-width: unset;
|
||||
margin: 10px 64px;
|
||||
}
|
||||
body.home .container:not(.is-max-desktop):not(.is-max-widescreen) {
|
||||
max-width: 1152px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item:nth-child(3n+1):not(.wide) .media {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.list-item:nth-child(3n):not(.wide) .media {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.nav-urls .urls a, .nav-urls .urls span {
|
||||
color: white;
|
||||
background-color: #444;
|
||||
border-radius: 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#background {
|
||||
background-size: cover;
|
||||
padding: 80px 0 80px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.button:hover, a.button:hover, button.button:hover {
|
||||
opacity: 0.9 !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: whitesmoke;
|
||||
padding: 24px;
|
||||
border-radius: 5px;
|
||||
max-width: 960px;
|
||||
border: 1px solid #929293;
|
||||
}
|
||||
|
||||
.container.breadcrumbs, .container.header {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
padding: 0 24px 0 24px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.container.breadcrumbs a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.grid .list-item .headings .heading {
|
||||
padding-top: 10px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
|
||||
.nav.secondary {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.nav.secondary .nav-item{
|
||||
olor: #1d3cab !important;
|
||||
color: #8c827e !important;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.nav .nav-item:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.navs {
|
||||
border-bottom: 1px solid #4d4545;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.preview-cover {
|
||||
border-radius: 5px;
|
||||
opacity: 0.92;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
.a-carousel .preview-cover {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.preview-card .headings .heading {
|
||||
opacity: 0.85;
|
||||
padding: 12px;
|
||||
background-color: #242121;
|
||||
}
|
||||
|
||||
.preview-card .headings a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.grid .preview .headings a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.preview .title, .preview .title:not(:last-child) {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.nav .nav-menu {
|
||||
background-color: #6C7ED2;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#background {
|
||||
padding: 100px 0 50px 0;
|
||||
}
|
||||
|
||||
.container, .page .container {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
*/
|