Compare commits

...

14 Commits

Author SHA1 Message Date
Tomáš Mládek 8a2983fc10 default volume at 80% 2020-01-30 12:46:56 +01:00
Tomáš Mládek 1ea509d784 bump version, update CHANGELOG.md
(fix missing changelog link)
2020-01-12 21:12:32 +01:00
Tomáš Mládek e5b5ba12e0 fix volume actually not getting animated 2020-01-12 21:10:14 +01:00
Tomáš Mládek 336a637b9f remove logo 2020-01-12 20:08:41 +01:00
Tomáš Mládek 6acf04782c edit changelog, bump version 2020-01-12 20:03:51 +01:00
Tomáš Mládek 0ce39e8ac1 fix chrome styling 2020-01-12 20:01:44 +01:00
Tomáš Mládek 3983620d81 slider: now controls maximum volume; styling; its own component
also added SCSS
2020-01-12 20:00:39 +01:00
Tomáš Mládek 2c7afa6273 revert "responsiveness" in channel size 2020-01-12 19:31:19 +01:00
Tomáš Mládek 284ff09fd8 Add LICENSE 2020-01-12 11:44:32 +00:00
Tomáš Mládek cfbb286052 add CHANGELOG.md 2020-01-12 12:43:15 +01:00
Tomáš Mládek 90a0c16754 bump version 2020-01-12 12:36:35 +01:00
Tomáš Mládek 90e912a50f clear button 2020-01-12 12:20:00 +01:00
Tomáš Mládek daeb71b93c responzify 2020-01-12 12:15:16 +01:00
Tomáš Mládek e05a203867 move channel title to top; fix vertical range input layout 2020-01-11 10:59:55 +01:00
8 changed files with 1033 additions and 42 deletions

36
CHANGELOG.md Normal file
View File

@ -0,0 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.3] - 2020-01-12
### Fixed
- Fixed volume actually not getting animated after first slider movement.
## [0.1.2] - 2020-01-12
### Added
- Slider styling
### Changed
- Slider now controls *maximum* volume independent of animated volume (i.e. acts as a volume fader)
## [0.1.1] - 2020-01-12
### Added
- Clear button for URLs.
- Basic layout responsiveness.
### Changed
- Move channel title to top.
## [0.1.0] - 2020-01-11
### Added
- First usable version - single animation mode yet; YouTube only.
[Unreleased]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.3...master
[0.1.3]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.2...v0.1.3
[0.1.2]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.1...v0.1.2
[0.1.1]: https://gitlab.com/tmladek/noise-maker/compare/v0.1.0...v0.1.1
[0.1.0]: https://gitlab.com/tmladek/noise-maker/-/tags/v0.1.0

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2020, Tomáš Mládek
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

806
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "noisemaker",
"version": "0.1.0",
"version": "0.1.3",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@ -14,10 +14,12 @@
"youtube-player": "^5.5.2"
},
"devDependencies": {
"@types/youtube-player": "^5.5.1",
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-typescript": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"@types/youtube-player": "^5.5.1",
"node-sass": "^4.13.0",
"sass-loader": "^8.0.1",
"typescript": "~3.5.3",
"vue-template-compiler": "^2.6.10"
}

View File

@ -18,6 +18,7 @@ import {PlayingState} from "@/common";
</label>
<input :id="`url-input-${names[i-1]}`" type="text" v-model="urls[i - 1]"/>
</div>
<button class="channel-urls-clear-button" @click="urls = Array(N_CHANNELS).fill('')">CLEAR</button>
</div>
</div>
<div class="controls">
@ -46,7 +47,7 @@ export default class App extends Vue {
private playing: PlayingState = PlayingState.STOPPED;
private names = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu"];
private urls = Array(this.N_CHANNELS).fill("");
private volumes = Array(this.N_CHANNELS).fill(50);
private volumes = Array(this.N_CHANNELS).fill(100);
private states = Array(this.N_CHANNELS).fill(LoadingState.UNLOADED);
private animateVolumeStart?: Date;
@ -133,11 +134,14 @@ h1 {
.channels-wrapper {
display: flex;
width: 80%;
flex-wrap: wrap;
}
.channels {
display: flex;
padding: 0 1rem;
padding: 0 1rem 1rem 0;
justify-content: space-between;
flex-grow: 1;
}
.channel {
@ -147,14 +151,20 @@ h1 {
.channel-urls {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 100%;
flex-grow: 1;
}
.channel-urls-clear-button {
max-width: 5em;
}
.url-input {
display: flex;
width: 100%;
align-items: center;
margin: .2rem 0;
}
.url-input label {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,14 +1,9 @@
<template>
<div :class="['channel', `channel-${loadingState}`]">
<div class="volume-wrapper">
<!--suppress HtmlFormInputWithoutLabel -->
<input class="volume" :disabled="loadingState === 'unloaded'"
type="range" min="0" max="100" v-model="volume"/>
</div>
<div class="description">
<div class="name">{{name}}</div>
<div class="title">{{loadingState === "loading" ? "Loading..." : title}}</div>
</div>
<div class="name">{{name}}</div>
<VolumeSlider :value="volume" :disabled="loadingState === 'unloaded'"
@valueChange="(actualValue) => {actualVolume = actualValue}"/>
<div class="title">{{loadingState === "loading" ? "Loading..." : title}}</div>
<div class="youtube-player" ref="ytpl"/>
</div>
</template>
@ -21,22 +16,26 @@ import YouTubePlayerFactory from "youtube-player";
import {YouTubePlayer} from "youtube-player/dist/types";
import PlayerStates from "youtube-player/dist/constants/PlayerStates";
import {LoadingState, PlayingState} from "@/common";
import VolumeSlider from "@/components/VolumeSlider.vue";
enum SOURCE {
YouTube,
DIRECT
}
@Component
@Component({
components: {VolumeSlider}
})
export default class Channel extends Vue {
@Prop() public name: string = "Channel";
@Prop() public volume: number = 50;
@Prop() public name!: string;
@Prop() public volume: number = 100;
@Prop() public url: string | undefined;
@Prop() public playing: PlayingState = PlayingState.STOPPED;
private youtubePlayer?: YouTubePlayer;
private loadingState = LoadingState.UNLOADED;
private title = "";
private actualVolume = this.volume;
private get source() {
if (this.url) {
@ -51,7 +50,7 @@ export default class Channel extends Vue {
if (this.youtubePlayer) {
switch (this.playing) {
case PlayingState.PLAYING:
this.youtubePlayer.setVolume(this.volume);
this.youtubePlayer.setVolume(this.actualVolume);
this.youtubePlayer.playVideo();
break;
case PlayingState.STOPPED:
@ -106,10 +105,10 @@ export default class Channel extends Vue {
}
}
@Watch("volume")
private onVolumeChange() {
@Watch("actualVolume")
private onActualVolumeChange() {
if (this.youtubePlayer) {
this.youtubePlayer.setVolume(this.volume);
this.youtubePlayer.setVolume(this.actualVolume);
}
}
@ -177,27 +176,10 @@ export default class Channel extends Vue {
background: lightgray;
}
.volume-wrapper {
--height: 180px;
--width: 30px;
height: var(--height);
width: var(--width);
display: block;
}
.volume {
position: relative;
width: var(--height);
height: var(--width);
top: calc(var(--height) / 2);
left: calc(var(--height) / 2 * -1 + var(--width) / 2);
transform: rotate(270deg);
}
.name {
text-align: center;
font-weight: bold;
padding: 2px 0;
}
.title {

View File

@ -0,0 +1,136 @@
<template>
<div class="volume-wrapper">
<!--suppress HtmlFormInputWithoutLabel -->
<input class="volume" :disabled="disabled"
type="range" min="0" max="100" v-model="maxValue"
:style="`--value: ${value}%; --max-value: ${maxValue}%; --actual-value: ${value * (maxValue / 100)}%`"/>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue, Watch} from "vue-property-decorator";
@Component
export default class Channel extends Vue {
private name = "VolumeSlider";
private maxValue = 80;
@Prop() public value!: number;
@Prop() public disabled!: boolean;
private mounted() {
this.onMaxValueChange();
}
@Watch("value")
@Watch("maxValue")
private onMaxValueChange() {
this.$emit("valueChange", this.value * (this.maxValue / 100), this.maxValue);
}
};
</script>
<style lang="scss" scoped>
.volume-wrapper {
--height: 180px;
--width: 30px;
height: var(--height);
width: var(--width);
display: flex;
align-items: center;
}
.volume {
position: relative;
width: var(--height);
min-width: var(--height);
height: var(--width);
min-height: var(--width);
left: calc(var(--height) / 2 * -1 + var(--width) / 2);
transform: rotate(270deg);
margin: 0;
}
input[type=range] {
-webkit-appearance: none;
background: transparent;
&:focus {
outline: none;
}
}
@mixin range-track() {
height: 10px;
cursor: pointer;
background: linear-gradient(90deg,
black 0,
black var(--actual-value), white var(--actual-value),
white var(--max-value), transparent var(--max-value), transparent 100%);
border: 1px solid #010101;
}
@mixin range-thumb {
border: 1px solid #000000;
height: 24px;
width: 12px;
background: #ffffff;
border-radius: 0;
cursor: pointer;
}
input[type=range]::-webkit-slider-runnable-track {
@include range-track;
}
input[type=range]::-moz-range-track {
@include range-track;
}
input[type=range]::-webkit-slider-thumb {
@include range-thumb;
-webkit-appearance: none;
margin-top: -8px;
}
input[type=range]::-moz-range-thumb {
@include range-thumb;
}
/*
input[type=range]::-ms-track {
width: 100%;
height: 8.4px;
cursor: pointer;
background: transparent;
border-color: transparent;
border-width: 16px 0;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #2a6495;
border: 0.2px solid #010101;
border-radius: 2.6px;
}
input[type=range]::-ms-fill-upper {
background: #3071a9;
border: 0.2px solid #010101;
border-radius: 2.6px;
}
input[type=range]::-ms-thumb {
border: 1px solid #000000;
height: 36px;
width: 16px;
border-radius: 3px;
background: #ffffff;
cursor: pointer;
}
input[type=range]:focus::-ms-fill-lower {
background: #3071a9;
}
input[type=range]:focus::-ms-fill-upper {
background: #367ebd;
}*/
</style>