+
-
diff --git a/src/app/components/chord-view/chord-view.component.scss b/src/app/components/chord-view/chord-view.component.scss
index 72ae06d..73b0da8 100644
--- a/src/app/components/chord-view/chord-view.component.scss
+++ b/src/app/components/chord-view/chord-view.component.scss
@@ -1,12 +1,19 @@
+@import '../overlay-common';
+
.transpose {
- margin-left: 25px;
display: flex;
- flex-direction: column;
- font-size: 3rem;
+ justify-content: center;
+ align-items: center;
+ font-size: 2rem;
+
mat-icon {
- font-size: 3rem;
+ transform: scale(1.5);
}
- span {
- margin-left: 17px;
+
+ @media screen and (min-width: $mobile-breakpoint) {
+ font-size: 3rem;
+ mat-icon {
+ transform: scale(2);
+ }
}
}
diff --git a/src/app/components/chord-view/chordpro.pipe.ts b/src/app/components/chord-view/chordpro.pipe.ts
index fa6c36b..754c110 100644
--- a/src/app/components/chord-view/chordpro.pipe.ts
+++ b/src/app/components/chord-view/chordpro.pipe.ts
@@ -52,6 +52,8 @@ export class ChordProPipe implements PipeTransform {
notesSharpNotation = {};
notesFlatNotation = {};
+ private fillHtml = `
`;
+
decodeHTML(value: string) {
const tempElement = document.createElement('div');
tempElement.innerHTML = value;
@@ -146,40 +148,103 @@ export class ChordProPipe implements PipeTransform {
// becuase it gets messed up when a chord is placed on it..
// shouldn't be relevant if we actually get chordpro format
song = this.decodeHTML(song);
- const comp = this;
if (!song) {
return '';
}
let chordText = '';
- let lastChord = '';
- if (!song.match(comp.chordRegex)) {
+ if (!song.match(this.chordRegex)) {
return `
${song}
`;
}
- song.split(comp.chordRegex).forEach((part, index) => {
- if (index % 2 === 0) {
- // text
- if (lastChord) {
- chordText += `
${part.substring(0, 1)}${part.substring(1)}`;
- lastChord = '';
- } else {
- chordText += part;
+ // Processing backwards so we can better identify where chords should overlap lyric letters
+ // or insert a space in lyrics
+ song.split(/\n/).reverse().forEach((row, index) => {
+
+ chordText = `
' : ''}` + chordText;
+ const rowParts = row.split(this.chordRegex);
+ let lastPart;
+
+ for (let i = rowParts.length - 1, r = 0, isFirst = true; i >= -1; i--, r++ ) {
+ if (!isFirst) {
+ chordText = this._processChordRow(nHalfSteps, rowParts[i], i, lastPart, r) + chordText;
}
- } else {
+
+ lastPart = rowParts[i];
+ isFirst = false;
+ }
+
+ chordText = '
' + chordText;
+ });
+ return `
${chordText}
`;
+ }
+
+ protected _processChordRow(nHalfSteps, part, index, lastPart, reverseIndex) {
+ if (index % 2 !== 0) {
+ if (index > 0) {
// chord
- lastChord = part.replace(/[[]]/, '');
+ let chord = part.replace(/[[]]/, '');
if (nHalfSteps !== 0) {
- lastChord = lastChord.split('/').map(chord => {
- const chordRoot = comp.chordRoot(chord);
- const newRoot = comp.transposeChord(chordRoot, nHalfSteps);
- return newRoot + comp.restOfChord(chord);
+ chord = chord.split('/').map(chordPart => {
+ const chordRoot = this.chordRoot(chordPart);
+ const newRoot = this.transposeChord(chordRoot, nHalfSteps);
+ return newRoot + this.restOfChord(chordPart);
}).join('/');
}
// use proper symbols
- lastChord = lastChord.replace(/b/g, '♭');
- lastChord = lastChord.replace(/#/g, '♯');
+ chord = chord.replace(/b/g, '♭');
+ chord = chord.replace(/#/g, '♯');
+ const textFirstLetter = `${lastPart.substring(0, 1)}`;
+ const textRest = lastPart.substring(1);
+ const isChordMusicKey = chord.startsWith('=');
+ const chordLength = chord.length;
+ const isFirstChordAfterRealWord = (lastPart.length && (reverseIndex === 1));
+ const shouldOverlapChord = (!isChordMusicKey) && (isFirstChordAfterRealWord || (lastPart.length > chord.length));
+ const isChordOnly = !lastPart.trim().length;
+
+ const chordClass = (shouldOverlapChord && 'overlap-chord' || '') + (isChordOnly && ' chord-only' || '');
+
+ const textLength = 1 + textRest.length;
+ const fillHtmlNeededLength = (!shouldOverlapChord && (textLength < chordLength)) ? chordLength - 1 : (chordLength - 1);
+ let fillHtml = !shouldOverlapChord && !isChordMusicKey ? this._makeFillHtml(fillHtmlNeededLength || 1) : '';
+ let fillHtmlLength = fillHtml?.length ?? 0;
+ const finalTextLength = 1 + textRest.length + fillHtmlLength;
+ const needExtraFill = (!shouldOverlapChord && (finalTextLength <= chordLength));
+
+ if (needExtraFill) {
+ fillHtml = this._makeFillHtml(fillHtmlLength + 1);
+ fillHtmlLength++;
+ chord += ' ';
+ }
+
+ if (!lastPart || (textFirstLetter === ' ')) {
+ fillHtml = ' '.repeat(fillHtmlLength);
+ }
+
+ if ((fillHtmlLength === 1 && chordLength < 2)) {
+ // To match text separator
+ chord += ' ';
+ }
+
+ return `
${chord}${textFirstLetter}${fillHtml}${textRest}`;
+ } else {
+ return `${lastPart}`;
}
- });
- return `
${chordText}
`;
+ }
+ return '';
+ }
+
+ protected _makeFillHtml(length) {
+ if (length >= 3) {
+ let text = '';
+ const middle = Math.floor(length / 2);
+
+ for (let i = 0; i < length; i++) {
+ text += (i === middle) ? '\u2014' : ' ';
+ }
+
+ return text;
+ } else {
+ return '\u00B7' + (length === 2 ? ' ' : '');
+ }
}
}
diff --git a/src/app/components/chord-view/chordpro.scss b/src/app/components/chord-view/chordpro.scss
index 0540bcd..7757995 100644
--- a/src/app/components/chord-view/chordpro.scss
+++ b/src/app/components/chord-view/chordpro.scss
@@ -1,23 +1,93 @@
+@import '../overlay-common';
+
.song {
white-space: pre-wrap;
+
.with-chords {
- line-height: 2;
+ line-height: 1;
+ font-family: monospace;
+ padding-top: 0.45em; // To avoid chord overlapping top bar
+ @include slide-font-size(0.85, 0.725);
+
+ > span {
+ vertical-align: bottom;
+ }
}
-
- span[data-chord]:before {
- position: relative;
- top: -1em;
+
+ &-row {
display: inline-block;
- content: attr(data-chord);
- width: 0;
- color: yellow;
+ }
+
+ span[data-chord]{
+ display: inline;
+ position: relative;
+ white-space: nowrap;
+
+ .text, .chord {
+ line-height: 1em;
+ height: 1em;
+ line-height: 2.4em;
+ }
+
+ .first-letter {
+ white-space: pre-wrap;
+ }
+
+ .chord {
+ color: yellow;
+ display: inline-block;
+ transform: translateY(-100%);
+ white-space: pre;
+ }
+
+ // Chords that invades next text
+ &.overlap-chord {
+ position: relative;
+
+ .chord {
+ width: 0;
+ }
+ }
+
+ &:not(.overlap-chord) {
+ display: inline-flex;
+ flex-direction: column;
+
+ .text {
+ position: absolute;
+ left: 0;
+ }
+ }
+
+ // Chords without text
+ &.chord-only {
+ display: inline-flex;
+ flex-direction: column;
+ margin-right: 0.3em;
+
+ .chord {
+ position: static;
+ }
+ }
+
}
}
.nextSlides {
.song {
- span[data-chord]:before {
- color: gray;
+ span[data-chord] {
+ .chord {
+ color: gray;
+ }
+
+ .fill .fill-inner {
+ background-color: gray;
+ }
}
+
+ }
+
+ .slide .with-chords {
+ @include slide-font-size(0.75, 0.65);
}
}
\ No newline at end of file
diff --git a/src/app/components/overlay.scss b/src/app/components/overlay.scss
index d3950e5..a620375 100644
--- a/src/app/components/overlay.scss
+++ b/src/app/components/overlay.scss
@@ -1,3 +1,5 @@
+@import "./overlay-common";
+
.overlay {
background: black;
width: 100%;
@@ -9,71 +11,158 @@
overflow: hidden;
color: white;
display: flex;
+ justify-content: flex-start;
flex-direction: row;
- justify-content: space-between;
+
+ &-content {
+ width: 100%;
+ max-width: 100%;
+ flex: 1;
+
+ @media (orientation: portrait) {
+ overflow: hidden;
+ flex: 1;
+ }
+
+ .tags {
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ color: green;
+ align-items: center;
+ @include slide-font-size(1);
+
+ @media screen and (min-width: $mobile-breakpoint) {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ font-size: 3rem;
+ }
+
+ span {
+ margin-left: 1rem;
+ &.active {
+ color: lightgreen;
+ font-weight: bold;
+ }
+ }
+ }
+
+ }
+
+ .sidebar {
+ padding: 0.8rem;
+ max-width: 30%;
+ margin-top: 1em;
+ overflow-y: auto;
+
+ .notes {
+ @include slide-font-size(0.9);
+ line-height: 1;
+
+ color: salmon;
+ text-align: right;
+ }
+
+ @media screen and (min-width: $mobile-breakpoint) {
+ margin-top: 4rem;
+ }
+
+ @media (orientation: portrait) {
+ max-width: none;
+ max-height: 20%;
+ margin-bottom: 3.75rem;
+ background-color: rgb(64, 64, 64);
+
+ .notes {
+ text-align: left;
+ }
+ }
+ }
+
+ @media (orientation: portrait) {
+ flex-direction: column;
+ flex-wrap: wrap;
+ }
+
+ .slide {
+ line-height: 1.2;
+ white-space: pre-line;
+ margin: 0;
+
+ &.first {
+ margin-top: 1rem;
+ }
+
+ @include slide-font-size();
+ }
+
}
-.sidebar {
- margin: 1rem;
+.toolbar {
+ padding: 0.8rem;
display: flex;
- flex-direction: column;
- justify-content: space-between;
- width: 30%;
-}
+ justify-content: flex-start;
+ align-items: center;
+ width: auto;
+ gap: 1.5rem;
-.time {
- font-size: 3rem;
- color: yellow;
- text-align: right;
-}
+ .back-button {
+ background: #fff;
+ }
-.notes {
- margin-top: 1em;
- font-size: 3rem;
- line-height: 3rem;
- color: salmon;
- text-align: right;
+ @media screen and (max-width: ($mobile-breakpoint - 0.125px)){
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(64, 64, 64, 0.5);
+ backdrop-filter: blur(2px);
+ padding: 0.6rem;
+ }
+
+ @media screen and (min-width: $mobile-breakpoint) {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+
+ @media screen and (max-width: ($mobile-breakpoint - 0.125px)) and (orientation: landscape) {
+ left: auto;
+ border-top-left-radius: 0.5rem;
+ }
}
.close {
text-align: right;
}
-.tags {
- margin-top: 1rem;
- margin-bottom: 1rem;
+.time {
+ color: yellow;
+ text-align: right;
+ white-space: nowrap;
+ flex: 1;
display: flex;
- flex-direction: row;
- justify-content: flex-start;
- color: green;
- font-size: 4rem;
- span {
- margin-left: 1rem;
- &.active {
- color: lightgreen;
- font-weight: bold;
- }
- }
-}
+ justify-content: flex-end;
+ align-items: center;
+ padding-right: 0.8rem;
+ font-size: 2rem;
-.slide {
- font-size: 3rem;
- white-space: pre-line;
- margin: 0;
- &.first {
- margin-top: 1rem;
+ @media screen and (min-width: $mobile-breakpoint) {
+ font-size: 3rem;
}
}
.container {
- margin-left: 1rem;
+ margin: 0 1rem;
}
.nextSlides {
- font-size: 2rem;
margin-top: 1rem;
color: grey;
+
.slide {
- font-size: 2rem;
+ @include slide-font-size(0.75);
}
}
diff --git a/src/app/components/stage-view/stage-view.component.html b/src/app/components/stage-view/stage-view.component.html
index 48ac9db..e750275 100644
--- a/src/app/components/stage-view/stage-view.component.html
+++ b/src/app/components/stage-view/stage-view.component.html
@@ -1,5 +1,5 @@
-
+
{{ tag.text }}
@@ -26,11 +26,23 @@
-
diff --git a/src/app/components/stage-view/stage-view.component.scss b/src/app/components/stage-view/stage-view.component.scss
index 330f55c..eb57666 100644
--- a/src/app/components/stage-view/stage-view.component.scss
+++ b/src/app/components/stage-view/stage-view.component.scss
@@ -18,3 +18,11 @@
.next-slides-text {
font-size: 1.4rem;
}
+
+.toolbar {
+ .show-notes {
+ &-disabled {
+ background: white;
+ }
+ }
+}
diff --git a/src/app/components/stage-view/stage-view.component.ts b/src/app/components/stage-view/stage-view.component.ts
index cd81399..294b347 100644
--- a/src/app/components/stage-view/stage-view.component.ts
+++ b/src/app/components/stage-view/stage-view.component.ts
@@ -19,6 +19,7 @@ export class StageViewComponent implements OnInit {
activeSlide = 0;
tags: Tag[] = [];
time = new Date();
+ showNotes = true;
constructor(public openlpService: OpenLPService) {
setInterval(() => this.time = new Date(), 1000);