Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preview + Stream Stats #90

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,65 @@ Built files will be in `dist/` directory, serve these with your favourite webser
* Clone
* Install dependencies
* `npm run dev`


## External Components
There is a new External panel which allows you to load an external VueJS component by specifying the url to a compiled umd component. This allows a fuller integration and ability to access and interact with the store. This is different than the Frame panel which embeds an external page in and iframe which sandboxes it away from the rest of the app.

For example, here is a super simple component that dumps the stream stats, and changes scenes to a passed in scene name. You can see we have full access to the store like normal.

```vue
<template>
<div class="flex-grow button-grid has-per-row-1 overflow-y-auto">
<pre>{{ dump }}</pre>
<button
class="button is-inactive"
@click="switchScene(scene)"
>
Set Current Scene to {{ scene }}
</button>
</div>
</template>

<script>
import { mapActions, mapState } from 'vuex'

export default {
name: 'Dump',
props: ['scene'],
data: () => ({}),
computed: {
...mapState('obs', {
dump: state => state.stream
}),
},
methods: {
...mapActions('obs', {
setScene: 'scenes/current'
}),

switchScene(name) {
this.setScene({name})
}
}
}
</script>

<style scoped>

</style>
```

Next, we use the vue-cli-service to build this as a stand-alone component:

```console
npx vue-cli-service build --target lib --formats umd-min --no-clean --dest extras/components/dump --name "dump" extras/components/dump/dump.vue
```

Then back in the app, add an External Component panel to your layout, and open the settings. Supply it the url to the rendered component file, style url if relevant, and then a json object that will be parsed and passed in as props. In our example we want the name of a scene to switch to as the `scene` prop, so add that in as proper JSON:

```json
{"scene": "main"}
```

and voila!
6 changes: 5 additions & 1 deletion src/assets/styles/components/alert.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.alert {
@apply .bg-red-300 .border-l-4 .border-red-500 .text-red-800 .p-4 mb-2;
@apply .border-l-4 .p-4 mb-2 .bg-red-300 .border-red-500 .text-red-800;

&.success {
@apply .bg-green-300 .border-green-500 .text-green-800;
}

&:last-child {
@apply .mb-0;
Expand Down
3 changes: 3 additions & 0 deletions src/assets/styles/components/buttons.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
&.has-icon {
@apply p-2 text-2xl;
}
&.is-disabled {
pointer-events: none;
}
}
@screen md {
.button-ui {
Expand Down
3 changes: 3 additions & 0 deletions src/assets/styles/themes/dark-blue.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
}
}
}
& .button.is-disabled {
--text-opacity: .5;
}

& a {
@apply .text-blue-500;
Expand Down
3 changes: 3 additions & 0 deletions src/assets/styles/themes/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
}
}
}
& .button.is-disabled {
--text-opacity: .5;
}

& a {
@apply .text-blue-600;
Expand Down
31 changes: 30 additions & 1 deletion src/components/panel-list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ const PANELS_INFO = [
}
}
},
{
name: 'Preview',
description: 'Auto Updating Preview of Current Scene',
icon: 'photo-video',
iconSettings: {},
type: 'Preview',
defaults() {
return {
delay: 1
}
}
},
{
name: 'Scenes Switcher',
description: 'Switch the current scene',
Expand Down Expand Up @@ -94,14 +106,31 @@ const PANELS_INFO = [
description: 'Manage stream & recording status',
icon: 'dot-circle',
iconSettings: {},
type: 'Stream'
type: 'Stream',
defaults() {
return {
direction: 'row'
}
}
},
{
name: 'Frame',
description: 'Embed any webpage',
icon: 'window-maximize',
iconSettings: {},
type: 'Iframe'
},
{
name: 'External Component',
description: 'Load an external Vue Component',
icon: 'file-code',
iconSettings: {},
type: 'External',
defaults() {
return {
url: false
}
}
}
]

Expand Down
153 changes: 153 additions & 0 deletions src/components/panels/external.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<template>
<panel-wrapper :content-class="['flex flex-col']">
<template slot="name">
External Component
<small
v-if="label"
class="block italic"
>{{ label }}</small>
</template>

<Component
:is="computedComponent"
v-bind="props"
>
did you forget the url?
</Component>

<template #settings>
<div class="field">
<label
:for="`settings-${id}-label`"
class="label"
>Label</label>
<input
:id="`settings-${id}-label`"
v-model="label"
class="input"
type="text"
>
</div>
<div class="field">
<label
:for="`settings-${id}-component-url`"
class="label"
>Component URL</label>
<input
:id="`settings-${id}-component-url`"
v-model="componentUrl"
class="input"
type="text"
>
</div>
<div class="field">
<label
:for="`settings-${id}-style-url`"
class="label"
>Style URL</label>
<input
:id="`settings-${id}-style-url`"
v-model="styleUrl"
class="input"
type="text"
>
</div>
<div class="field">
<label
:for="`settings-${id}-props`"
class="label"
>Props (JSON)</label>
<textarea
:id="`settings-${id}-props`"
v-model="propsEdit"
class="input"
/>
</div>
</template>
</panel-wrapper>
</template>

<script>
import panelMixin from '@/mixins/panel'
import {loadExternalComponent, loadExternalStyle} from '@/util'

export default {
mixins: [panelMixin],
data: () => ({
computedComponent: null
}),
computed: {
props: {
get() {
return this.settings.props || {}
}
},
propsEdit: {
get() {
return JSON.stringify(this.settings.props) || '{}'
},
set(value) {
this.setSetting('props', JSON.parse(value))
}
},
componentUrl: {
get() {
if (this.settings.componentUrl) {
return this.settings.componentUrl
}

return ''
},
set(value) {
this.setSetting('componentUrl', value)
}
},
styleUrl: {
get() {
if (this.settings.styleUrl) {
return this.settings.styleUrl
}

return ''
},
set(value) {
this.setSetting('styleUrl', value)
}
},
label: {
get() {
if (this.settings.label) {
return this.settings.label
}

return ''
},
set(value) {
this.setSetting('label', value)
}
}
},
watch: {
componentUrl: {
immediate: true,
handler(newUrl, oldUrl = '') {
if (newUrl === oldUrl) {
return
}

this.computedComponent = () => loadExternalComponent(newUrl)
}
},
styleUrl: {
immediate: true,
handler(newUrl, oldUrl = '') {
if (newUrl === oldUrl) {
return
}

loadExternalStyle(newUrl)
}
}
}
}
</script>
4 changes: 4 additions & 0 deletions src/components/panels/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Dynamic component resolver

import External from './external'
import Grid from './grid'
import Iframe from './iframe'
import Invalid from './invalid'
import Preview from './preview'
import Scenes from './scenes'
import Sources from './sources'
import Mixer from './mixer'
Expand All @@ -12,9 +14,11 @@ import Durations from './durations'
import panelMixin from '@/mixins/panel'

const components = {
External,
Grid,
Iframe,
Invalid,
Preview,
Scenes,
Sources,
Mixer,
Expand Down
Loading