Skip to content

Commit a1c56ab

Browse files
Merge pull request #13 from markschwartzkopf/reorder-feeds
Reorder feeds
2 parents b8965f3 + fe6d565 commit a1c56ab

File tree

4 files changed

+189
-56
lines changed

4 files changed

+189
-56
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gdq-viewport-assign",
3-
"version": "0.9.1",
3+
"version": "0.9.2",
44
"description": "",
55
"main": "dist/index.js",
66
"scripts": {

src/icons.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const icons = {
3030
left: `data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20fill%3D%27none%27%20stroke%3D%27%23d2d2d2%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27%20stroke-width%3D%272%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27m6%209%206%206%206-6%27%20transform%3D%27matrix(0%20-1.7072%20-1.7538%200%2032.495%2032.495)%27%2F%3E%3C%2Fsvg%3E`,
3131
right: `data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20fill%3D%27none%27%20stroke%3D%27%23d2d2d2%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27%20stroke-width%3D%272%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27m6%209%206%206%206-6%27%20transform%3D%27matrix(0%201.7072%201.7538%200%20-8.519%20-8.48)%27%2F%3E%3C%2Fsvg%3E`,
3232
addRow: `data:image/svg+xml,%3Csvg%20fill%3D%27none%27%20stroke-linejoin%3D%27round%27%20stroke-linecap%3D%27round%27%20stroke-width%3D%272%27%20viewBox%3D%270%200%2024%2024%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cdefs%3E%3Cmask%20id%3D%27a%27%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27%23fff%27%2F%3E%3Ccircle%20r%3D%278%27%20cx%3D%2712%27%20cy%3D%2716%27%20fill%3D%27%23000%27%2F%3E%3C%2Fmask%3E%3Cmask%20id%3D%27b%27%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27%23fff%27%2F%3E%3Cpath%20d%3D%27M9%2016h6m-3%203v-6%27%20stroke%3D%27%23000%27%2F%3E%3C%2Fmask%3E%3C%2Fdefs%3E%3Cpath%20d%3D%27M1%202v12h22V2Z%27%20stroke%3D%27%23d2d2d2%27%20mask%3D%27url%28%23a%29%27%2F%3E%3Ccircle%20r%3D%276%27%20cx%3D%2712%27%20cy%3D%2716%27%20fill%3D%27%23d2d2d2%27%20mask%3D%27url%28%23b%29%27%2F%3E%3C%2Fsvg%3E`,
33-
removeRow: 'data:image/svg+xml,%3Csvg%20fill%3D%27none%27%20stroke-linejoin%3D%27round%27%20stroke-linecap%3D%27round%27%20stroke-width%3D%272%27%20viewBox%3D%270%200%2024%2024%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cdefs%3E%3Cmask%20id%3D%27a%27%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27%23fff%27%2F%3E%3Ccircle%20r%3D%278%27%20cx%3D%2712%27%20cy%3D%2716%27%20fill%3D%27%23000%27%2F%3E%3C%2Fmask%3E%3Cmask%20id%3D%27b%27%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27%23fff%27%2F%3E%3Cpath%20d%3D%27M9%2016h6%27%20stroke%3D%27%23000%27%2F%3E%3C%2Fmask%3E%3C%2Fdefs%3E%3Cpath%20d%3D%27M1%202v12h22V2Z%27%20stroke%3D%27%23d2d2d2%27%20mask%3D%27url%28%23a%29%27%2F%3E%3Ccircle%20r%3D%276%27%20cx%3D%2712%27%20cy%3D%2716%27%20fill%3D%27%23d2d2d2%27%20mask%3D%27url%28%23b%29%27%2F%3E%3C%2Fsvg%3E'
33+
removeRow: 'data:image/svg+xml,%3Csvg%20fill%3D%27none%27%20stroke-linejoin%3D%27round%27%20stroke-linecap%3D%27round%27%20stroke-width%3D%272%27%20viewBox%3D%270%200%2024%2024%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cdefs%3E%3Cmask%20id%3D%27a%27%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27%23fff%27%2F%3E%3Ccircle%20r%3D%278%27%20cx%3D%2712%27%20cy%3D%2716%27%20fill%3D%27%23000%27%2F%3E%3C%2Fmask%3E%3Cmask%20id%3D%27b%27%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27%23fff%27%2F%3E%3Cpath%20d%3D%27M9%2016h6%27%20stroke%3D%27%23000%27%2F%3E%3C%2Fmask%3E%3C%2Fdefs%3E%3Cpath%20d%3D%27M1%202v12h22V2Z%27%20stroke%3D%27%23d2d2d2%27%20mask%3D%27url%28%23a%29%27%2F%3E%3Ccircle%20r%3D%276%27%20cx%3D%2712%27%20cy%3D%2716%27%20fill%3D%27%23d2d2d2%27%20mask%3D%27url%28%23b%29%27%2F%3E%3C%2Fsvg%3E',
34+
blank: `data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20fill%3D%27%23d2d2d2%27%20viewBox%3D%270%200%208%208%27%3E%3C%2Fsvg%3E`,
3435
};
3536

3637
export { icons };

src/main.ts

Lines changed: 185 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ const gdqGreen2 = [0, 255, 0];
99
let screenshotBase64 = '';
1010
let connectedToOBS = false;
1111
let obsConnectionError = '';
12-
let cropItem: null | (sceneItemRef & crop & { width: number; height: number }) =
13-
null;
12+
let obsUpdateTimeout: NodeJS.Timeout | null = null;
13+
let cropItem:
14+
| null
15+
| (sceneItemRef & crop & { width: number; height: number }) = null;
1416
let cropSide: 'left' | 'right' | 'top' | 'bottom' | null = null;
1517
let targetCrop: crop | null = null;
1618
let initialCrop: crop | null = null;
@@ -398,7 +400,14 @@ const reInitEvents = [
398400
function subscribeToChanges() {
399401
if (!subscribed)
400402
for (let i = 0; i < reInitEvents.length; i++) {
401-
obs.on(reInitEvents[i], initOBS);
403+
obs.on(reInitEvents[i], () => {
404+
if (obsUpdateTimeout) {
405+
clearTimeout(obsUpdateTimeout);
406+
}
407+
obsUpdateTimeout = setTimeout(() => {
408+
initOBS();
409+
}, 200);
410+
});
402411
}
403412
subscribed = true;
404413
}
@@ -543,14 +552,15 @@ function populateViewportsFromActiveFeed() {
543552
.send('GetSceneItemList', { sceneName: selectedFeedsScene })
544553
.then(async (data) => {
545554
sceneItemList = data.sceneItems;
555+
const viewportFeeds: viewport['assignedFeeds'] = [];
546556
for (let i = 0; i < data.sceneItems.length; i++) {
547557
await obs
548558
.send('GetSceneItemProperties', {
549559
'scene-name': selectedFeedsScene,
550560
item: { id: data.sceneItems[i].itemId },
551561
})
552562
.then((data) => {
553-
const viewportFeed: viewport['assignedFeeds'][number] = {
563+
viewportFeeds.push({
554564
'scene-name': selectedFeedsScene,
555565
item: { id: data.itemId, name: data.name },
556566
type: sceneItemList[i].sourceKind,
@@ -560,61 +570,97 @@ function populateViewportsFromActiveFeed() {
560570
bottom: data.crop.bottom,
561571
width: data.sourceWidth,
562572
height: data.sourceHeight,
563-
};
564-
let assigned = false;
565-
for (let i = 0; i < currentSceneViewports.length; i++) {
573+
x: data.position.x,
574+
y: data.position.y,
575+
boundsWidth: data.bounds.x,
576+
boundsHeight: data.bounds.y,
577+
});
578+
})
579+
.catch(obsError);
580+
}
581+
let i = 0;
582+
let productiveLoop = false;
583+
while (i < viewportFeeds.length) {
584+
let assigned = false;
585+
for (let j = 0; j < currentSceneViewports.length; j++) {
586+
if (
587+
viewportSearchBoxes[j].x == viewportFeeds[i].x &&
588+
viewportSearchBoxes[j].y == viewportFeeds[i].y
589+
) {
590+
if (
591+
viewportSearchBoxes[j].width == viewportFeeds[i].boundsWidth &&
592+
viewportSearchBoxes[j].height == viewportFeeds[i].boundsHeight
593+
) {
594+
currentSceneViewports[j].assignedFeeds.push(viewportFeeds[i]);
566595
if (
567-
viewportSearchBoxes[i].x == data.position.x &&
568-
viewportSearchBoxes[i].y == data.position.y
569-
) {
570-
if (
571-
viewportSearchBoxes[i].width == data.bounds.x &&
572-
viewportSearchBoxes[i].height == data.bounds.y
573-
) {
574-
currentSceneViewports[i].assignedFeeds.push(viewportFeed);
575-
if (
576-
currentSceneViewports[i].rows > 1 ||
577-
currentSceneViewports[i].columns > 1
578-
)
579-
viewportSearchBoxes[i] = getViewPortBoundingBoxes(
580-
currentSceneViewports[i]
581-
)[currentSceneViewports[i].assignedFeeds.length];
596+
currentSceneViewports[j].rows > 1 ||
597+
currentSceneViewports[j].columns > 1
598+
)
599+
viewportSearchBoxes[j] = getViewPortBoundingBoxes(
600+
currentSceneViewports[j]
601+
)[currentSceneViewports[j].assignedFeeds.length];
602+
if (!viewportSearchBoxes[j])
603+
viewportSearchBoxes[j] = {
604+
x: -1,
605+
y: -1,
606+
width: -1,
607+
height: -1,
608+
};
609+
productiveLoop = true;
610+
assigned = true;
611+
viewportFeeds.splice(i, 1);
612+
break;
613+
} else if (currentSceneViewports[j].assignedFeeds.length == 0) {
614+
const possibleWidth: number[] = [NaN]; //array index corresponds to number of columns, which can't be zero
615+
for (let k = 1; k <= 4; k++) {
616+
possibleWidth.push(
617+
Math.round(currentSceneViewports[j].width / k)
618+
);
619+
}
620+
const columns = possibleWidth.indexOf(
621+
viewportFeeds[i].boundsWidth
622+
);
623+
if (columns > 0) {
624+
const possibleHeight: number[] = [NaN]; //array index corresponds to number of rows, which can't be zero
625+
for (let k = 1; k <= 4; k++) {
626+
possibleHeight.push(
627+
Math.round(currentSceneViewports[j].height / k)
628+
);
629+
}
630+
const rows = possibleHeight.indexOf(
631+
viewportFeeds[i].boundsHeight
632+
);
633+
if (rows > 0) {
634+
currentSceneViewports[j].rows = rows;
635+
currentSceneViewports[j].columns = columns;
636+
currentSceneViewports[j].assignedFeeds.push(viewportFeeds[i]);
637+
viewportSearchBoxes[j] = getViewPortBoundingBoxes(
638+
currentSceneViewports[j]
639+
)[currentSceneViewports[j].assignedFeeds.length];
640+
if (!viewportSearchBoxes[j])
641+
viewportSearchBoxes[j] = {
642+
x: -1,
643+
y: -1,
644+
width: -1,
645+
height: -1,
646+
};
647+
productiveLoop = true;
582648
assigned = true;
649+
viewportFeeds.splice(i, 1);
583650
break;
584-
} else if (currentSceneViewports[i].assignedFeeds.length == 0) {
585-
const possibleWidth: number[] = [NaN];
586-
for (let j = 1; j <= 4; j++) {
587-
possibleWidth.push(
588-
Math.round(currentSceneViewports[i].width / j)
589-
);
590-
}
591-
const columns = possibleWidth.indexOf(data.bounds.x);
592-
if (columns > 0) {
593-
const possibleHeight: number[] = [NaN];
594-
for (let j = 1; j <= 4; j++) {
595-
possibleHeight.push(
596-
Math.round(currentSceneViewports[i].height / j)
597-
);
598-
}
599-
const rows = possibleHeight.indexOf(data.bounds.y);
600-
if (rows > 0) {
601-
currentSceneViewports[i].rows = rows;
602-
currentSceneViewports[i].columns = columns;
603-
currentSceneViewports[i].assignedFeeds.push(viewportFeed);
604-
viewportSearchBoxes[i] = getViewPortBoundingBoxes(
605-
currentSceneViewports[i]
606-
)[currentSceneViewports[i].assignedFeeds.length];
607-
assigned = true;
608-
break;
609-
}
610-
}
611651
}
612652
}
613653
}
614-
if (!assigned) unassignedFeeds.push(viewportFeed);
615-
})
616-
.catch(obsError);
654+
}
655+
}
656+
//if (!assigned) unassignedFeeds.push(viewportFeeds[i]);
657+
if (!assigned) i++;
658+
if (i == viewportFeeds.length && productiveLoop == true) {
659+
i = 0;
660+
productiveLoop = false;
661+
}
617662
}
663+
unassignedFeeds = viewportFeeds;
618664
})
619665
.catch(obsError);
620666
}
@@ -759,7 +805,8 @@ async function refreshViewportsDiv() {
759805
if (
760806
currentSceneViewports[i] &&
761807
currentSceneViewports[i].rows >
762-
currentSceneViewports[i].assignedFeeds.length
808+
currentSceneViewports[i].assignedFeeds.length &&
809+
currentSceneViewports[i].assignedFeeds.length > 0
763810
)
764811
currentSceneViewports[i].rows =
765812
currentSceneViewports[i].assignedFeeds.length;
@@ -781,13 +828,88 @@ async function refreshViewportsDiv() {
781828
refreshCropDiv();
782829
};
783830
sourceDiv.appendChild(cropIcon);
831+
const upIcon = document.createElement('img');
832+
upIcon.classList.add('icon');
833+
upIcon.style.float = 'right';
834+
if (j > 0) {
835+
upIcon.src = icons.up;
836+
upIcon.onclick = () => {
837+
swapFeeds(viewportFeeds, j, j - 1);
838+
};
839+
} else upIcon.src = icons.blank;
840+
sourceDiv.appendChild(upIcon);
841+
if (j < viewportFeeds.length - 1) {
842+
const downIcon = document.createElement('img');
843+
downIcon.classList.add('icon');
844+
downIcon.style.float = 'right';
845+
downIcon.src = icons.down;
846+
downIcon.onclick = () => {
847+
swapFeeds(viewportFeeds, j, j + 1);
848+
};
849+
sourceDiv.appendChild(downIcon);
850+
}
784851
viewportSourcesDiv.appendChild(sourceDiv);
785852
}
786853
listDiv.appendChild(viewportSourcesDiv);
787854
}
788855
refreshFooter();
789856
}
790857

858+
function swapFeeds(
859+
viewportFeeds: typeof unassignedFeeds,
860+
index1: number,
861+
index2: number
862+
) {
863+
const feed1 = viewportFeeds[index1];
864+
const feed2 = viewportFeeds[index2];
865+
const swapX = feed2.x;
866+
const swapY = feed2.y;
867+
const swapWidth = feed2.boundsWidth;
868+
const swapHeight = feed2.boundsHeight;
869+
feed2.x = feed1.x;
870+
feed2.y = feed1.y;
871+
feed2.boundsWidth = feed1.boundsWidth;
872+
feed2.boundsHeight = feed1.boundsHeight;
873+
feed1.x = swapX;
874+
feed1.y = swapY;
875+
feed1.boundsWidth = swapWidth;
876+
feed1.boundsHeight = swapHeight;
877+
unsubscribeToChanges();
878+
obs
879+
.send('SetSceneItemProperties', {
880+
'scene-name': selectedFeedsScene,
881+
item: feed1.item,
882+
position: { x: feed1.x, y: feed1.y },
883+
scale: {},
884+
crop: {},
885+
bounds: {
886+
x: feed1.boundsWidth,
887+
y: feed1.boundsHeight,
888+
},
889+
})
890+
.then(() => {
891+
return obs.send('SetSceneItemProperties', {
892+
'scene-name': selectedFeedsScene,
893+
item: feed2.item,
894+
position: { x: feed2.x, y: feed2.y },
895+
scale: {},
896+
crop: {},
897+
bounds: {
898+
x: feed2.boundsWidth,
899+
y: feed2.boundsHeight,
900+
},
901+
});
902+
})
903+
.then(() => {
904+
const swap = viewportFeeds[index2];
905+
viewportFeeds[index2] = viewportFeeds[index1];
906+
viewportFeeds[index1] = swap;
907+
refreshViewportsDiv();
908+
subscribeToChanges();
909+
})
910+
.catch(console.error);
911+
}
912+
791913
async function removeUnassignedSources() {
792914
unsubscribeToChanges();
793915
for (let i = 0; i < unassignedFeeds.length; i++) {
@@ -840,6 +962,12 @@ async function arrangeViewportFeeds(viewport: viewport) {
840962
y: boxes[i].height,
841963
},
842964
})
965+
.then(() => {
966+
feed.x = boxes[i].x;
967+
feed.y = boxes[i].y;
968+
feed.boundsWidth = boxes[i].width;
969+
feed.boundsHeight = boxes[i].height;
970+
})
843971
.catch(console.error);
844972
}
845973
subscribeToChanges();
@@ -1167,6 +1295,10 @@ async function addSourceToViewport(
11671295
bottom: 0,
11681296
width: source.source_cx,
11691297
height: source.source_cy,
1298+
x: viewport.x,
1299+
y: viewport.y,
1300+
boundsWidth: viewport.width,
1301+
boundsHeight: viewport.height,
11701302
};
11711303
return obs.send('SetSceneItemProperties', {
11721304
'scene-name': scene,

types/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type viewport = {
2828
height: number;
2929
rows: number;
3030
columns: number;
31-
assignedFeeds: (sceneItemRef & crop & { type: string, width: number, height: number })[];
31+
assignedFeeds: (sceneItemRef & crop & { type: string, width: number, height: number, x: number, y: number, boundsWidth: number, boundsHeight: number })[];
3232
};
3333
type obsSceneItems = {
3434
itemId: number;

0 commit comments

Comments
 (0)