diff --git a/app/components/MixerVolmeter.vue.ts b/app/components/MixerVolmeter.vue.ts index 5eb489fb5f98..9c12383fe550 100644 --- a/app/components/MixerVolmeter.vue.ts +++ b/app/components/MixerVolmeter.vue.ts @@ -1,6 +1,6 @@ import Vue from 'vue'; import { Component, Prop } from 'vue-property-decorator'; -import { Subscription } from 'rxjs/Subscription'; +import { Subscription } from 'rxjs'; import { AudioSource } from 'services/audio'; import { Inject } from 'util/injector'; import { CustomizationService } from 'services/customization'; diff --git a/app/components/SceneSelector.vue b/app/components/SceneSelector.vue index 5984cd6ecec5..9665a4b183e7 100644 --- a/app/components/SceneSelector.vue +++ b/app/components/SceneSelector.vue @@ -57,6 +57,15 @@ {{ $t('scenes.sceneCollectionSelectionDescription') }} + + +
+ {{ $t('scenes.scenePreset') }} +
+
+ {{ $t('scenes.scenePresetDescription') }} +
+
diff --git a/app/components/SceneSelector.vue.ts b/app/components/SceneSelector.vue.ts index a7489e6519b2..5f77ff296d92 100644 --- a/app/components/SceneSelector.vue.ts +++ b/app/components/SceneSelector.vue.ts @@ -149,4 +149,8 @@ export default class SceneSelector extends Vue { get helpTipDismissable() { return EDismissable.SceneCollectionsHelpTip; } + + get scenePresetHelpTipDismissable() { + return EDismissable.ScenePresetHelpTip; + } } diff --git a/app/components/shared/inputs/ValidatedForm.vue.ts b/app/components/shared/inputs/ValidatedForm.vue.ts index d2bf23c0f4a6..9facd5041f57 100644 --- a/app/components/shared/inputs/ValidatedForm.vue.ts +++ b/app/components/shared/inputs/ValidatedForm.vue.ts @@ -5,7 +5,7 @@ import uuid from 'uuid'; import { ErrorField } from 'vee-validate'; import { BaseInput } from './BaseInput'; import { IInputMetadata } from './index'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; /** * VeeValidate doesn't support slots https://github.com/baianat/vee-validate/issues/325 diff --git a/app/components/windows/Projector.vue.ts b/app/components/windows/Projector.vue.ts index 9139a0d58ba5..ffc6629b9435 100644 --- a/app/components/windows/Projector.vue.ts +++ b/app/components/windows/Projector.vue.ts @@ -8,7 +8,7 @@ import { ISourcesServiceApi } from 'services/sources'; import electron from 'electron'; import Util from 'services/utils'; import { $t } from 'services/i18n'; -import { Subscription } from 'rxjs/subscription'; +import { Subscription } from 'rxjs'; @Component({ components: { diff --git a/app/components/windows/Settings.vue.ts b/app/components/windows/Settings.vue.ts index bfd13221f353..fe854b726ea3 100644 --- a/app/components/windows/Settings.vue.ts +++ b/app/components/windows/Settings.vue.ts @@ -1,6 +1,6 @@ import Vue from 'vue'; import { Component, Watch } from 'vue-property-decorator'; -import { Subscription } from 'rxjs/Subscription'; +import { Subscription } from 'rxjs'; import { Inject } from '../../util/injector'; import ModalLayout from '../ModalLayout.vue'; import NavMenu from '../shared/NavMenu.vue'; diff --git a/app/components/windows/SourceProperties.vue.ts b/app/components/windows/SourceProperties.vue.ts index fe40482bf91c..8f2af10d926e 100644 --- a/app/components/windows/SourceProperties.vue.ts +++ b/app/components/windows/SourceProperties.vue.ts @@ -9,7 +9,7 @@ import ModalLayout from 'components/ModalLayout.vue'; import Display from 'components/shared/Display.vue'; import GenericForm from 'components/obs/inputs/GenericForm.vue'; import { $t } from 'services/i18n'; -import { Subscription } from 'rxjs/subscription'; +import { Subscription } from 'rxjs'; import electron from 'electron'; @Component({ diff --git a/app/i18n/en-US/scenes.json b/app/i18n/en-US/scenes.json index 523dc5846da1..b2bc168c2366 100644 --- a/app/i18n/en-US/scenes.json +++ b/app/i18n/en-US/scenes.json @@ -12,6 +12,7 @@ "defaultTitle": "Output", "manageAll": "Manage All", "sceneCollectionSelectionDescription": "This is where your Scene Collections live. Clicking the title will dropdown a menu where you can view & manage.", + "scenePresetDescription": "There are sample sources added. Coordinate your own broadcast by editing them freely!", "removeSceneCollectionConfirmTitle": "Remove Scene Collection", "removeSceneCollectionConfirm": "Are you sure you want to remove %{collectionName}?", "createSceneProjector": "Create Scene Projector", @@ -24,6 +25,7 @@ "newSceneCollectionName": "New Scene Collection", "alreadyTakenName": "That name is already taken", "sceneCollections": "Scene Collections", + "scenePreset": "Scene Preset", "sourcesTooltip": "The building blocks of your scene.", "addSourceTooltip": "Add a new Source to your Scene.", "removeSourcesTooltip": "Remove Sources from your Scene.", diff --git a/app/i18n/en-US/source-props.json b/app/i18n/en-US/source-props.json index b737eb2a68b1..de5bcd8a4452 100644 --- a/app/i18n/en-US/source-props.json +++ b/app/i18n/en-US/source-props.json @@ -276,7 +276,7 @@ } }, "dshow_input": { - "name": "Video Capture Device", + "name": "WebCam / Video Input", "description": "Select from your build in USB webcam or an external.", "activate": { "name": "Deactivate" diff --git a/app/i18n/ja-JP/scenes.json b/app/i18n/ja-JP/scenes.json index 998e001c2731..68f5b5117ef3 100644 --- a/app/i18n/ja-JP/scenes.json +++ b/app/i18n/ja-JP/scenes.json @@ -12,6 +12,7 @@ "defaultTitle": "", "manageAll": "シーンコレクションの管理", "sceneCollectionSelectionDescription": "こちらはシーンコレクションがある場所です。 タイトルをクリックすると、表示および管理できるメニューがプルダウン表示されます。", + "scenePresetDescription": "サンプルとなるソースを追加しています。自由に編集して、自分だけの放送をコーディネートしてみましょう!", "removeSceneCollectionConfirmTitle": "シーンコレクションを削除する", "removeSceneCollectionConfirm": "本当に%{collectionName}を削除してもよろしいですか?", "createSceneProjector": "シーンプロジェクターを作成する", @@ -24,6 +25,7 @@ "newSceneCollectionName": "シーンコレクション", "alreadyTakenName": "その名前は既に使用されています。", "sceneCollections": "シーンコレクション", + "scenePreset": "シーンプリセット", "sourcesTooltip": "シーンを作成するためのものです。", "addSourceTooltip": "シーンに新しいソースを追加します。", "removeSourcesTooltip": "シーンからソースを削除します。", diff --git a/app/i18n/ja-JP/source-props.json b/app/i18n/ja-JP/source-props.json index 5f9e35bad34a..74d3343941ec 100644 --- a/app/i18n/ja-JP/source-props.json +++ b/app/i18n/ja-JP/source-props.json @@ -276,7 +276,7 @@ } }, "dshow_input": { - "name": "映像キャプチャデバイス", + "name": "Webカメラ/映像入力機器", "description": "シーンに内蔵/外付けのWebカメラやキャプチャーボートを通したゲーム画面などを追加します。", "activate": { "name": "無効化" diff --git a/app/services-manager.ts b/app/services-manager.ts index 132d190e97bb..a206fa4d7af0 100644 --- a/app/services-manager.ts +++ b/app/services-manager.ts @@ -35,9 +35,7 @@ import Utils from './services/utils'; import { commitMutation } from './store'; import traverse from 'traverse'; import { ObserveList } from './util/service-observer'; -import { Subject } from 'rxjs/Subject'; -import { Subscription } from 'rxjs/Subscription'; -import { Observable } from 'rxjs/Observable'; +import { Subject, Subscription, Observable } from 'rxjs'; import { VideoEncodingOptimizationService } from 'services/video-encoding-optimizations'; import { DismissablesService } from 'services/dismissables'; import { SceneCollectionsService } from 'services/scene-collections'; diff --git a/app/services/audio/audio-api.ts b/app/services/audio/audio-api.ts index 230eabe7a261..8885d0ae877e 100644 --- a/app/services/audio/audio-api.ts +++ b/app/services/audio/audio-api.ts @@ -1,9 +1,8 @@ import * as obs from '../../../obs-api'; -import { Subscription } from 'rxjs/Subscription'; +import { Subscription, Observable } from 'rxjs'; import { TObsFormData } from 'components/obs/inputs/ObsInput'; import { ISource } from '../sources/sources-api'; -import { Observable } from 'rxjs/Observable'; export interface IAudioSourcesState { audioSources: Dictionary; diff --git a/app/services/audio/audio.ts b/app/services/audio/audio.ts index f077aea4324e..f2723c736d15 100644 --- a/app/services/audio/audio.ts +++ b/app/services/audio/audio.ts @@ -1,6 +1,5 @@ import Vue from 'vue'; -import { Subject } from 'rxjs/Subject'; -import { Subscription } from 'rxjs/Subscription'; +import { Subject, Subscription, Observable } from 'rxjs'; import { mutation, StatefulService, ServiceHelper } from 'services/stateful-service'; import { SourcesService, ISource, Source } from 'services/sources'; import { ScenesService } from 'services/scenes'; @@ -16,7 +15,6 @@ import { IAudioDevice, IAudioServiceApi, IAudioSource, IAudioSourceApi, IAudioSourcesState, IFader, IVolmeter } from './audio-api'; -import { Observable } from 'rxjs/Observable'; import { $t } from 'services/i18n'; import uuid from 'uuid/v4'; import { omit } from 'lodash'; diff --git a/app/services/crash-reporter.ts b/app/services/crash-reporter.ts index 0fd12c31ef8c..43bd5ff092b8 100644 --- a/app/services/crash-reporter.ts +++ b/app/services/crash-reporter.ts @@ -2,7 +2,7 @@ import { Service } from 'services/service'; import electron from 'electron'; import { StreamingService, EStreamingState } from 'services/streaming'; import { Inject } from 'util/injector'; -import { Subscription } from 'rxjs/subscription'; +import { Subscription } from 'rxjs'; import path from 'path'; import fs from 'fs'; import { UsageStatisticsService } from 'services/usage-statistics'; diff --git a/app/services/customization/customization-api.ts b/app/services/customization/customization-api.ts index 3cec37be4593..833e5d8444bc 100644 --- a/app/services/customization/customization-api.ts +++ b/app/services/customization/customization-api.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { TObsFormData } from 'components/obs/inputs/ObsInput'; export interface ICustomizationServiceState { diff --git a/app/services/customization/customization.ts b/app/services/customization/customization.ts index 6e9be0e82838..ca0df9ae9d38 100644 --- a/app/services/customization/customization.ts +++ b/app/services/customization/customization.ts @@ -1,4 +1,4 @@ -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; import { PersistentStatefulService } from '../persistent-stateful-service'; import { mutation } from '../stateful-service'; import { diff --git a/app/services/dismissables.ts b/app/services/dismissables.ts index 42dff79a46c6..6b7840aada22 100644 --- a/app/services/dismissables.ts +++ b/app/services/dismissables.ts @@ -3,21 +3,29 @@ import { mutation } from './stateful-service'; import Vue from 'vue'; export enum EDismissable { - SceneCollectionsHelpTip = 'scene_collections_help_tip' + SceneCollectionsHelpTip = 'scene_collections_help_tip', + ScenePresetHelpTip = 'scene_preset_help_tip' } +const InitiallyDismissed = new Set([ + EDismissable.ScenePresetHelpTip, +]); + interface IDismissablesServiceState { [key: string]: boolean; } /** - * A dismissable is anything that can be dismissed and should - * never show up again, like a help tip. + * A dismissable is anything that is shown by default, can be dismissed and + * show up again if needed, like a help tip. */ export class DismissablesService extends PersistentStatefulService { shouldShow(key: EDismissable): boolean { + if (!(key in this.state)) { + return !InitiallyDismissed.has(key); + } return !this.state[key]; } @@ -25,6 +33,10 @@ export class DismissablesService extends PersistentStatefulService this.dismiss(EDismissable[key])); } @@ -34,4 +46,9 @@ export class DismissablesService extends PersistentStatefulService(); collectionRemoved = new Subject(); @@ -116,9 +120,60 @@ export class SceneCollectionsService extends Service } else { await this.create(); } + + const scenes = this.scenesService.scenes; + if (this.collections.length === 1 && scenes.length === 1 && scenes[0].getItems().length === 0) { + // シーンが一つで空であるため、シーンプリセットをインストールする + await this.installPresetSceneCollection(); + } + this.initialized = true; } + /// install preset scene collection into active scene collection + async installPresetSceneCollection() { + // 既存scene を消す + this.scenesService.scenes.forEach(scene => scene.remove(true)); + + // キャンバス解像度を 1280x720 に変更する + const CanvasResolution = '1280x720'; + const video = this.settingsService.getSettingsFormData('Video'); + if (video) { + const setting = this.settingsService.findSetting(video, 'Untitled', 'Base'); + if (setting) { + if (setting.value !== CanvasResolution) { + console.log(`Canvas resolution is ${setting.value}. reset to ${CanvasResolution}.`); + setting.value = CanvasResolution; + this.settingsService.setSettings('Video', video); + } + } + } + + // this.load() を参考に + + this.startLoadingOperation(); + + const jsonData = this.stateService.readCollectionFile(ScenePresetId); + // Preset file作成上の注意 + // - image pathを相対にする + // - default audio sourcesを削除する + + const root: RootNode = parse(jsonData, NODE_TYPES); + // この間で読み込んだ内容を加工できる + // - デフォルトソースが重複している場合に除去する? 現在はpreset側で除去している + await root.load(); + this.hotkeysService.bindHotkeys(); + + await this.save(); + + this.finishLoadingOperation(); + + // activate tips for preset scene collection + this.dismissablesService.reset(EDismissable.ScenePresetHelpTip); + // dismiss initial scene collections help tip if not yet(since its position is overlapped) + this.dismissablesService.dismiss(EDismissable.SceneCollectionsHelpTip); + } + /** * Should be called when a new user logs in. If the user has * scene collections backed up on the server, it will reset @@ -134,6 +189,7 @@ export class SceneCollectionsService extends Service async deinitialize() { this.disableAutoSave(); await this.save(); + this.tcpServerService.stopRequestsHandling(); await this.deloadCurrentApplicationState(); await this.stateService.flushManifestFile(); } @@ -273,7 +329,7 @@ export class SceneCollectionsService extends Service name: string, progressCallback?: (info: IDownloadProgress) => void ) { - this.startLoadingOperation(); + this.startLoadingOperation(); // memo: calling this in loadOverlay() too const pathName = await this.overlaysPersistenceService.downloadOverlay( url, @@ -499,8 +555,6 @@ export class SceneCollectionsService extends Service private async deloadCurrentApplicationState() { if (!this.initialized) return; - this.tcpServerService.stopRequestsHandling(); - this.collectionWillSwitch.next(); this.disableAutoSave(); @@ -538,6 +592,7 @@ export class SceneCollectionsService extends Service this.windowsService.closeChildWindow(); this.windowsService.closeAllOneOffs(); this.appService.startLoading(); + this.tcpServerService.stopRequestsHandling(); this.disableAutoSave(); } @@ -586,6 +641,9 @@ export class SceneCollectionsService extends Service await this.saveCurrentApplicationStateAs(id); this.stateService.ADD_COLLECTION(id, name, new Date().toISOString()); this.collectionAdded.next(this.collections.find(coll => coll.id === id)); + + // コレクションが増えたら scene preset help tipを消す + this.dismissablesService.dismiss(EDismissable.ScenePresetHelpTip); } /** diff --git a/app/services/scene-collections/state.ts b/app/services/scene-collections/state.ts index 6c6411a72502..421a9ca9a842 100644 --- a/app/services/scene-collections/state.ts +++ b/app/services/scene-collections/state.ts @@ -12,6 +12,8 @@ interface ISceneCollectionsManifest { collections: ISceneCollectionsManifestEntry[]; } +export const ScenePresetId = 'preset/basic'; + /** * This is a submodule of the scene collections service that handles * state/manifest mutations and persistence. @@ -195,6 +197,9 @@ export class SceneCollectionsStateService extends StatefulService< } getCollectionFilePath(id: string) { + if (id === ScenePresetId) { + return path.join('scene-presets', 'basic.json'); + } return path.join(this.collectionsDirectory, `${id}.json`); } diff --git a/app/services/scenes/scenes-api.ts b/app/services/scenes/scenes-api.ts index cc6085e9a680..1525a85c24b7 100644 --- a/app/services/scenes/scenes-api.ts +++ b/app/services/scenes/scenes-api.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { ISourceApi, TSourceType, ISource } from 'services/sources'; import { ISelection, TNodesList } from 'services/selection'; @@ -7,12 +7,14 @@ import { ISelection, TNodesList } from 'services/selection'; */ export interface IScenesServiceApi { createScene(name: string, options?: ISceneCreateOptions): ISceneApi; + makeSceneActive(id: string): boolean; removeScene(id: string): IScene; scenes: ISceneApi[]; activeScene: ISceneApi; activeSceneId: string; getScene(id: string): ISceneApi; + getSceneByName(name: string): ISceneApi; getScenes(): ISceneApi[]; getModel(): IScenesState; suggestName(name: string): string; diff --git a/app/services/scenes/scenes.ts b/app/services/scenes/scenes.ts index cb1118b8fa64..87f041b606db 100644 --- a/app/services/scenes/scenes.ts +++ b/app/services/scenes/scenes.ts @@ -14,7 +14,7 @@ import { } from './index'; import { SourcesService, ISource } from 'services/sources'; import electron from 'electron'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; import { Inject } from 'util/injector'; import * as obs from '../../../obs-api'; import { $t } from 'services/i18n'; @@ -170,6 +170,10 @@ export class ScenesService extends StatefulService implements ISce return !this.state.scenes[id] ? null : new Scene(id); } + getSceneByName(name: string) { + const foundId = Object.keys(this.state.scenes).find(id => this.state.scenes[id].name === name); + return foundId ? this.getScene(foundId) : null; + } getSceneItem(sceneItemId: string) { for (const scene of this.scenes) { diff --git a/app/services/selection/selection.ts b/app/services/selection/selection.ts index a383b370ae75..5610c4670ded 100644 --- a/app/services/selection/selection.ts +++ b/app/services/selection/selection.ts @@ -14,7 +14,7 @@ import { $t } from 'services/i18n'; import { Inject } from '../../util/injector'; import { shortcut } from '../shortcuts'; import { ISelection, ISelectionServiceApi, ISelectionState, TNodesList } from './selection-api'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; import Utils from '../utils'; import { Source } from '../sources'; import { CenteringAxis } from 'util/ScalableRectangle'; diff --git a/app/services/service.ts b/app/services/service.ts index 194d3cadb0a0..46b598ce47c0 100644 --- a/app/services/service.ts +++ b/app/services/service.ts @@ -2,7 +2,7 @@ * simple singleton service implementation * @see original code http://stackoverflow.com/a/26227662 */ -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; const singleton = Symbol(); const singletonEnforcer = Symbol(); diff --git a/app/services/sources/sources-api.ts b/app/services/sources/sources-api.ts index bbcf30c5f8e2..45f004597f98 100644 --- a/app/services/sources/sources-api.ts +++ b/app/services/sources/sources-api.ts @@ -1,6 +1,6 @@ import { IPropertyManager } from './properties-managers/properties-manager'; import { IObsListOption, TObsFormData } from 'components/obs/inputs/ObsInput'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import * as obs from '../../../obs-api'; export interface ISource extends IResource { diff --git a/app/services/sources/sources.ts b/app/services/sources/sources.ts index 13ce88468bbf..94590c3b0780 100644 --- a/app/services/sources/sources.ts +++ b/app/services/sources/sources.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import Vue from 'vue'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; import { IObsListOption, setupConfigurableDefaults, TObsValue } from 'components/obs/inputs/ObsInput'; import { StatefulService, mutation } from 'services/stateful-service'; import * as obs from '../../../obs-api'; @@ -317,7 +317,7 @@ export class SourcesService extends StatefulService implements IS this.UPDATE_SOURCE(size); } this.updateSourceFlags(source, update.outputFlags); - }); + }); } } diff --git a/app/services/streaming/streaming-api.ts b/app/services/streaming/streaming-api.ts index feb0ebdbf4ba..7bfa6b34287e 100644 --- a/app/services/streaming/streaming-api.ts +++ b/app/services/streaming/streaming-api.ts @@ -1,4 +1,4 @@ -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; export enum EStreamingState { Offline = 'offline', diff --git a/app/services/streaming/streaming.ts b/app/services/streaming/streaming.ts index 0e060b35ce64..9a18cc920f9b 100644 --- a/app/services/streaming/streaming.ts +++ b/app/services/streaming/streaming.ts @@ -4,7 +4,7 @@ import { Inject } from 'util/injector'; import moment from 'moment'; import { SettingsService } from 'services/settings'; import { WindowsService } from 'services/windows'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; import * as electron from 'electron'; import { IStreamingServiceApi, diff --git a/app/services/transitions.ts b/app/services/transitions.ts index 0f7dab515667..cc7aa6e17471 100644 --- a/app/services/transitions.ts +++ b/app/services/transitions.ts @@ -12,7 +12,7 @@ import uuid from 'uuid/v4'; import { SceneCollectionsService } from 'services/scene-collections'; import { $t } from 'services/i18n'; import { DefaultManager } from 'services/sources/properties-managers/default-manager'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; export enum ETransitionType { Cut = 'cut_transition', diff --git a/app/services/user.ts b/app/services/user.ts index 6430405cca17..1de76f4c9ab6 100644 --- a/app/services/user.ts +++ b/app/services/user.ts @@ -16,9 +16,7 @@ import { import Raven from 'raven-js'; import { AppService } from 'services/app'; import { SceneCollectionsService } from 'services/scene-collections'; -import { Subject } from 'rxjs/Subject'; -import { Observable } from 'rxjs/Observable'; -import { mergeStatic } from 'rxjs/operator/merge'; +import { Subject, Observable, merge } from 'rxjs'; import { WindowsService } from 'services/windows'; import { cpu as systemInfoCpu, @@ -71,7 +69,7 @@ export class UserService extends PersistentStatefulService { userLogin = new Subject(); userLogout = new Subject(); - userLoginState: Observable = mergeStatic(this.userLogin, this.userLogout); + userLoginState: Observable = merge(this.userLogin, this.userLogout); init() { super.init(); diff --git a/app/services/video.ts b/app/services/video.ts index 2ce265b8dfe9..6969aec320a7 100644 --- a/app/services/video.ts +++ b/app/services/video.ts @@ -6,7 +6,7 @@ import { Inject } from '../util/injector'; import Utils from './utils'; import { WindowsService } from './windows'; import { ScalableRectangle } from '../util/ScalableRectangle'; -import { Subscription } from 'rxjs/Subscription'; +import { Subscription } from 'rxjs'; import { SelectionService } from 'services/selection'; const { remote } = electron; diff --git a/app/services/windows.ts b/app/services/windows.ts index 37a97b012c42..15b82ea84395 100644 --- a/app/services/windows.ts +++ b/app/services/windows.ts @@ -27,7 +27,7 @@ import { mutation, StatefulService } from 'services/stateful-service'; import electron from 'electron'; import Vue from 'vue'; import Util from 'services/utils'; -import { Subject } from 'rxjs/Subject'; +import { Subject } from 'rxjs'; import ExecuteInCurrentWindow from '../util/execute-in-current-window'; const { ipcRenderer, remote } = electron; diff --git a/bin/release/configs/type.d.ts b/bin/release/configs/type.d.ts index f575eb593477..d45b9c14466b 100644 --- a/bin/release/configs/type.d.ts +++ b/bin/release/configs/type.d.ts @@ -13,5 +13,6 @@ export type ReleaseConfig = { upload: { githubToken: string; s3BucketName: string; + s3KeyPrefix: string; }; }; diff --git a/bin/release/generatePatchNote.js b/bin/release/generatePatchNote.js index 88c062c17d9b..1d180cd83617 100644 --- a/bin/release/generatePatchNote.js +++ b/bin/release/generatePatchNote.js @@ -1,5 +1,7 @@ // @ts-check +const fs = require('fs'); +const path = require('path'); const OctoKit = require('@octokit/rest'); const sh = require('shelljs'); const colors = require('colors/safe'); @@ -24,13 +26,15 @@ const { collectPullRequestMerges, } = require('./scripts/patchNote'); +const pjson = JSON.parse(fs.readFileSync(path.resolve('./package.json'), 'utf-8')); + async function generateRoutine({ githubTokenForReadPullRequest }) { info(colors.magenta('|------------------------------|')); info(colors.magenta('| N Air Release Note Generator |')); info(colors.magenta('|------------------------------|')); - info('checking current tag ...'); - const previousTag = executeCmd('git describe --tags --abbrev=0').stdout.trim(); + info('checking current version ...'); + const previousVersion = pjson.version; const baseDir = executeCmd('git rev-parse --show-cdup', { silent: true }).stdout.trim(); const patchNoteFileName = `${baseDir}patch-note.txt`; @@ -42,19 +46,19 @@ async function generateRoutine({ githubTokenForReadPullRequest }) { log(`${previousPatchNote.version}\n${previousPatchNote.lines.join('\n')}`); info(`patch-note.txt for ${previousPatchNote.version} found`); - info(`current version: ${previousTag}`); + info(`current version: ${previousVersion}`); if (!await confirm('overwrite?', false)) { sh.exit(1); } } else { - info(`current version: ${previousTag}`); + info(`current version: ${previousVersion}`); } - const previousVersionContext = getVersionContext(previousTag); + const previousVersionContext = getVersionContext(previousVersion); const { channel, environment } = previousVersionContext; - log('current version', colors.cyan(previousTag)); + log('current version', colors.cyan(previousVersion)); log('environment', (environment === 'public' ? colors.red : colors.cyan)(environment)); log('channel', (channel === 'stable' ? colors.red : colors.cyan)(channel)); @@ -71,18 +75,18 @@ async function generateRoutine({ githubTokenForReadPullRequest }) { } const defaultVersion = generateNewVersion({ - previousTag, + previousVersion, }); log('\nestimated version', colors.cyan(defaultVersion)); const newVersion = await input('What should the new version number be?', defaultVersion); - const newVersionContext = getVersionContext(`v${newVersion}`); + const newVersionContext = getVersionContext(newVersion); if (!isSameVersionContext(previousVersionContext, newVersionContext)) { - log('version', colors.cyan(previousTag), ' -> ', colors.cyan(`v${newVersion}`)); + log('version', colors.cyan(previousVersion), ' -> ', colors.cyan(newVersion)); const environmentIsMatched = previousVersionContext.environment === newVersionContext.environment; const channelIsMatched = previousVersionContext.channel === newVersionContext.channel; - const colorize = flag => flag ? colors.red : colors.cyan; + const colorize = flag => (flag ? colors.red : colors.cyan); log( 'environment:', colorize(!environmentIsMatched)(environmentIsMatched ? 'matched ' : 'unmatched'), @@ -117,10 +121,10 @@ async function generateRoutine({ githubTokenForReadPullRequest }) { owner: 'n-air-app', repo: 'n-air-app', }, - previousTag + previousVersion ); - const directCommits = executeCmd(`git log --no-merges --first-parent --pretty=format:"%s (%t)" ${previousTag}..`, { + const directCommits = executeCmd(`git log --no-merges --first-parent --pretty=format:"%s (%t)" v${previousVersion}..`, { silent: true, }).stdout; const directCommitsNotes = directCommits ? `\nDirect Commits:\n${directCommits}` : ''; diff --git a/bin/release/mini-release.js b/bin/release/mini-release.js index 7f223df4b058..45f0be9dada6 100644 --- a/bin/release/mini-release.js +++ b/bin/release/mini-release.js @@ -6,7 +6,6 @@ const fs = require('fs'); const path = require('path'); const OctoKit = require('@octokit/rest'); -const inq = require('inquirer'); const sh = require('shelljs'); const colors = require('colors/safe'); const yaml = require('js-yaml'); @@ -30,9 +29,11 @@ const { const { uploadS3File, uploadToGithub, - uploadToSentry + uploadToSentry, } = require('./scripts/uploadArtifacts'); +const pjson = JSON.parse(fs.readFileSync(path.resolve('./package.json'), 'utf-8')); + /** * This is the main function of the script * @param {object} param0 @@ -309,23 +310,23 @@ async function releaseRoutine() { throw new Error(`patchNote is not found in ${patchNoteFileName}.`); } - const versionTag = `v${patchNote.version}`; - info(`patch-note.txt for ${versionTag} found`); + const nextVersion = patchNote.version; + info(`patch-note.txt for ${nextVersion} found`); - if (getTagCommitId(versionTag)) { - error(`Tag "${versionTag}" has already been released.`); + if (getTagCommitId(`v${nextVersion}`)) { + error(`Tag "v${nextVersion}" has already been released.`); info('Generate new patchNote with new version.'); info('If you want to retry current release, remove the tag and related release commit.'); - throw new Error(`Tag "${versionTag}" has already been released.`); + throw new Error(`Tag "${nextVersion}" has already been released.`); } - info('checking current tag ...'); - const previousTag = executeCmd('git describe --tags --abbrev=0').stdout.trim(); - const previousVersionContext = getVersionContext(previousTag); + info('checking current version ...'); + const previousVersion = pjson.version; + const previousVersionContext = getVersionContext(previousVersion); - const newVersionContext = getVersionContext(versionTag); + const newVersionContext = getVersionContext(nextVersion); if (!isSameVersionContext(previousVersionContext, newVersionContext)) { - const msg = `previous version ${previousTag} and releasing version ${versionTag} have different context.`; + const msg = `previous version ${previousVersion} and releasing version ${nextVersion} have different context.`; if (process.env.NAIR_IGNORE_VERSION_CONTEXT_CHECK) { await confirm(`${msg} Are you sure?`, false); } else { diff --git a/bin/release/scripts/patchNote.js b/bin/release/scripts/patchNote.js index fab46e36ba09..f560dc520c15 100644 --- a/bin/release/scripts/patchNote.js +++ b/bin/release/scripts/patchNote.js @@ -11,12 +11,12 @@ const { getTagCommitId } = require('./util'); // previous tag should be following rule: // v{major}.{minor}.{yyyymmdd}-[{channel}.]{ord}[internalMark] -const VERSION_REGEXP = /v(?\d+)\.(?\d+)\.(?\d{8})-((?\w+)\.)?(?\d+)(?d)?/; +const VERSION_REGEXP = /(?\d+)\.(?\d+)\.(?\d{8})-((?\w+)\.)?(?\d+)(?d)?/; -function parseVersionTag(tag) { +function parseVersion(tag) { const result = VERSION_REGEXP.exec(tag); if (result && result.groups) return result.groups; - throw new Error(`cannot parse a given tag: ${tag}`) + throw new Error(`cannot parse a given tag: ${tag}`); } /** @typedef {{ channel: 'stable' | 'unstable', environment: 'public' | 'internal' }} VersionContext */ @@ -26,7 +26,7 @@ function parseVersionTag(tag) { * @returns {VersionContext} */ function getVersionContext(tag) { - const result = parseVersionTag(tag); + const result = parseVersion(tag); if (result.channel === 'stable') { throw new Error('stable channel must have no prefix'); } @@ -68,12 +68,12 @@ function validateVersionContext({ } function generateNewVersion({ - previousTag, + previousVersion, now = Date.now(), }) { const { major, minor, date, channel, ord, internalMark - } = parseVersionTag(previousTag); + } = parseVersion(previousVersion); const today = moment(now).format('YYYYMMDD'); const newOrd = date === today ? parseInt(ord, 10) + 1 : 1; @@ -108,8 +108,8 @@ function writePatchNoteFile(patchNoteFileName, version, contents) { fs.writeFileSync(patchNoteFileName, body); } -async function collectPullRequestMerges({ octokit, owner, repo }, previousTag) { - const merges = executeCmd(`git log --oneline --merges ${previousTag}..`, { silent: true }).stdout; +async function collectPullRequestMerges({ octokit, owner, repo }, previousVersion) { + const merges = executeCmd(`git log --oneline --merges v${previousVersion}..`, { silent: true }).stdout; const promises = []; for (const line of merges.split(/\r?\n/)) { @@ -223,7 +223,7 @@ function readPatchNote({ } module.exports = { - parseVersionTag, + parseVersion, getVersionContext, generateNewVersion, isSameVersionContext, diff --git a/bin/release/scripts/patchNote.test.js b/bin/release/scripts/patchNote.test.js index 20c42e0b62db..f8e306741f04 100644 --- a/bin/release/scripts/patchNote.test.js +++ b/bin/release/scripts/patchNote.test.js @@ -1,13 +1,13 @@ -const { parseVersionTag, getVersionContext, validateVersionContext, generateNewVersion } = require('./patchNote'); +const { parseVersion, getVersionContext, validateVersionContext, generateNewVersion } = require('./patchNote'); const fixtures = { public: { - stable: 'v1.0.20190826-2', - unstable: 'v1.0.20190826-unstable.2', + stable: '1.0.20190826-2', + unstable: '1.0.20190826-unstable.2', }, internal: { - stable: 'v1.0.20190826-2d', - unstable: 'v1.0.20190826-unstable.2d', + stable: '1.0.20190826-2d', + unstable: '1.0.20190826-unstable.2d', }, }; const TODAY = new Date('2019-08-26'); @@ -46,7 +46,7 @@ for (const fixtureSet of channelEnvironmentSets()) { } test('バージョンがパースできる(public stable)', () => { - expect(parseVersionTag(fixtures.public.stable)).toMatchInlineSnapshot(` + expect(parseVersion(fixtures.public.stable)).toMatchInlineSnapshot(` Object { "channel": undefined, "date": "20190826", @@ -58,7 +58,7 @@ test('バージョンがパースできる(public stable)', () => { `); }); test('バージョンがパースできる(public unstable)', () => { - expect(parseVersionTag(fixtures.public.unstable)).toMatchInlineSnapshot(` + expect(parseVersion(fixtures.public.unstable)).toMatchInlineSnapshot(` Object { "channel": "unstable", "date": "20190826", @@ -70,7 +70,7 @@ test('バージョンがパースできる(public unstable)', () => { `); }); test('バージョンがパースできる(internal stable)', () => { - expect(parseVersionTag(fixtures.internal.stable)).toMatchInlineSnapshot(` + expect(parseVersion(fixtures.internal.stable)).toMatchInlineSnapshot(` Object { "channel": undefined, "date": "20190826", @@ -82,7 +82,7 @@ test('バージョンがパースできる(internal stable)', () => { `); }); test('バージョンがパースできる(internal unstable)', () => { - expect(parseVersionTag(fixtures.internal.unstable)).toMatchInlineSnapshot(` + expect(parseVersion(fixtures.internal.unstable)).toMatchInlineSnapshot(` Object { "channel": "unstable", "date": "20190826", @@ -95,11 +95,11 @@ test('バージョンがパースできる(internal unstable)', () => { }); test('stableチャンネルの場合はバージョン中のチャンネル部分があったらエラー', () => { - expect(() => getVersionContext('v1.0.20190826-stable.2')).toThrow(); + expect(() => getVersionContext('1.0.20190826-stable.2')).toThrow(); }); test('知らないチャンネルを名乗っていたらエラー', () => { - expect(() => getVersionContext('v1.0.20190826-hogehoge.2')).toThrow(); + expect(() => getVersionContext('1.0.20190826-hogehoge.2')).toThrow(); }); test('バージョンがパースできる(public stable)', () => { @@ -153,43 +153,43 @@ test('ふたつのVersionContextが同じか否か判定できる', () => { }); test('次のバージョンを生成する(当日、publicでstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.public.stable, now: TODAY })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.public.stable, now: TODAY })).toMatchInlineSnapshot( `"1.0.20190826-3"` ); }); test('次のバージョンを生成する(当日、publicでunstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.public.unstable, now: TODAY })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.public.unstable, now: TODAY })).toMatchInlineSnapshot( `"1.0.20190826-unstable.3"` ); }); test('次のバージョンを生成する(当日、internalでstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.internal.stable, now: TODAY })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.internal.stable, now: TODAY })).toMatchInlineSnapshot( `"1.0.20190826-3d"` ); }); test('次のバージョンを生成する(当日、internalでunstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.internal.unstable, now: TODAY })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.internal.unstable, now: TODAY })).toMatchInlineSnapshot( `"1.0.20190826-unstable.3d"` ); }); test('次のバージョンを生成する(別日、publicでstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.public.stable, now: TOMORROW })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.public.stable, now: TOMORROW })).toMatchInlineSnapshot( `"1.0.20190827-1"` ); }); test('次のバージョンを生成する(別日、publicでunstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.public.unstable, now: TOMORROW })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.public.unstable, now: TOMORROW })).toMatchInlineSnapshot( `"1.0.20190827-unstable.1"` ); }); test('次のバージョンを生成する(別日、internalでstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.internal.stable, now: TOMORROW })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.internal.stable, now: TOMORROW })).toMatchInlineSnapshot( `"1.0.20190827-1d"` ); }); test('次のバージョンを生成する(別日、internalでunstable)', () => { - expect(generateNewVersion({ previousTag: fixtures.internal.unstable, now: TOMORROW })).toMatchInlineSnapshot( + expect(generateNewVersion({ previousVersion: fixtures.internal.unstable, now: TOMORROW })).toMatchInlineSnapshot( `"1.0.20190827-unstable.1d"` ); }); diff --git a/electron-builder/stable.config.js b/electron-builder/stable.config.js index 7e73ccdb440e..f403d1447df4 100644 --- a/electron-builder/stable.config.js +++ b/electron-builder/stable.config.js @@ -14,6 +14,7 @@ const config = { 'obs-api' ], extraFiles: [ + 'scene-presets', 'LICENSE', 'AGREEMENT.sjis' ], diff --git a/package.json b/package.json index bf28b7679d0b..14c1471d6d31 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "package:internal-stable": "shx rm -rf dist && yarn install --cwd bin && node bin/convert-to-shiftjis.js AGREEMENT && build -w --x64 --config electron-builder/internal-stable.config.js", "package:internal-unstable": "shx rm -rf dist && yarn install --cwd bin && node bin/convert-to-shiftjis.js AGREEMENT && build -w --x64 --config electron-builder/internal-unstable.config.js", "release": "yarn install --cwd bin && node bin/release/mini-release.js", - "patch-note": "node bin/release/generatePatchNote.js", + "patch-note": "yarn install --cwd bin && node bin/release/generatePatchNote.js", "webfont": "yarn install --cwd bin && node bin/generate-webfont.js", "test": "node bin/i18n-early-check.js && tsc -p test && ava -v", "test:unit": "yarn test:unit:app && yarn test:unit:bin", @@ -83,7 +83,7 @@ "reflect-metadata": "^0.1.13", "request": "^2.85.0", "rimraf": "^2.6.1", - "rxjs": "^5.4.3", + "rxjs": "^6.5.3", "semver": "^5.5.1", "sl-vue-tree": "https://github.com/stream-labs/sl-vue-tree", "socket.io-client": "2.1.1", @@ -173,12 +173,12 @@ "style-loader": "^0.23.1", "terser-webpack-plugin": "^2.0.0", "ts-jest": "^24.0.2", - "ts-loader": "^6.0.1", + "ts-loader": "^6.2.1", "tslint": "^5.8.0", "tslint-config-prettier": "^1.14.0", "tslint-loader": "^3.6.0", "typedoc": "^0.9.0", - "typescript": "^3.4.5", + "typescript": "^3.7.2", "vue-loader": "^15.7.0", "vue-svg-loader": "^0.12.0", "vue-template-compiler": "^2.6.10", diff --git a/scene-presets/basic.json b/scene-presets/basic.json new file mode 100644 index 000000000000..a1dd711cb2e8 --- /dev/null +++ b/scene-presets/basic.json @@ -0,0 +1,452 @@ +{ + "schemaVersion": 1, + "nodeType": "RootNode", + "sources": { + "schemaVersion": 3, + "nodeType": "SourcesNode", + "items": [ + { + "id": "text_gdiplus_63fcf6f6-7648-404e-bdbf-8bef1d393e8c", + "name": "メッセージ内容", + "type": "text_gdiplus", + "settings": { + "align": "left", + "bk_color": 0, + "bk_opacity": 0, + "chatlog_lines": 6, + "color": 16777215, + "custom_font": "", + "extents_cx": 100, + "extents_cy": 100, + "extents_wrap": true, + "font": { + "face": "Meiryo UI", + "flags": 1, + "size": 48 + }, + "gradient_color": 16777215, + "gradient_dir": 90, + "gradient_opacity": 100, + "opacity": 100, + "outline_color": 16777215, + "outline_opacity": 100, + "outline_size": 2, + "text": "コメント募集中です!", + "valign": "top" + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + } + }, + { + "id": "image_source_f0ec48b4-a492-4706-9f3f-1320e44516d0", + "name": "背景画像", + "type": "image_source", + "settings": { + "file": "scene-presets\\basic\\images\\nair-preset-background.png", + "unload": false + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + } + }, + { + "id": "image_source_9c0f1fc5-e10b-4117-ad05-817c44e6a904", + "name": "装飾画像", + "type": "image_source", + "settings": { + "file": "scene-presets\\basic\\images\\nair-preset-bilibili.png", + "unload": false + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + } + }, + { + "id": "image_source_452bc18f-fcc6-4ca6-b70c-c8126fbc8c5a", + "name": "吹き出し画像", + "type": "image_source", + "settings": { + "file": "scene-presets\\basic\\images\\nair-preset-fukidashi.png", + "unload": false + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + } + }, + { + "id": "dshow_input_a1a3e9c4-493b-4b96-a217-19c37f014628", + "name": "Webカメラ", + "type": "dshow_input", + "settings": { + "active": true, + "audio_device_id": "マイク配列 (Realtek Audio):", + "audio_output_mode": 0, + "buffering": 0, + "color_range": "partial", + "color_space": "default", + "frame_interval": -1, + "last_resolution": "1280x720", + "last_video_device_id": "Integrated Webcam:\\\\?\\usb#22vid_0bda&pid_568c&mi_00#226&5e1df6&0&0000#22{65e8773d-8f56-11d0-a3b9-00a0c9223196}\\global", + "res_type": 0, + "video_format": 0 + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + }, + "deinterlaceMode": 0, + "deinterlaceFieldOrder": 0, + "forceMono": false, + "syncOffset": { + "sec": 0, + "nsec": 0 + }, + "audioMixers": 255, + "monitoringType": 0, + "mixerHidden": false + }, + { + "id": "image_source_7f92d474-3887-48a7-a96b-6d3cde692f81", + "name": "アイコン", + "type": "image_source", + "settings": { + "file": "scene-presets\\basic\\images\\nair-preset-twitter.png", + "unload": false + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + } + }, + { + "id": "text_gdiplus_487b9fe0-6a73-4eda-b3ad-31110e26d8e0", + "name": "ユーザー名", + "type": "text_gdiplus", + "settings": { + "align": "left", + "bk_color": 0, + "bk_opacity": 0, + "chatlog_lines": 6, + "color": 16777215, + "custom_font": "", + "extents_cx": 100, + "extents_cy": 100, + "extents_wrap": true, + "font": { + "face": "Meiryo UI", + "flags": 1, + "size": 48 + }, + "gradient_color": 16777215, + "gradient_dir": 90, + "gradient_opacity": 100, + "opacity": 100, + "outline_color": 16777215, + "outline_opacity": 100, + "outline_size": 2, + "text": "@n_air_taro", + "valign": "top" + }, + "volume": 1, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "muted": false, + "filters": { + "items": [] + }, + "propertiesManager": "default", + "propertiesManagerSettings": { + "mediaBackup": {} + } + } + ] + }, + "scenes": { + "schemaVersion": 1, + "nodeType": "ScenesNode", + "items": [ + { + "id": "scene_d8929ac0-3632-4225-b0ba-59a67227b388", + "name": "プリセット", + "sceneItems": { + "schemaVersion": 1, + "nodeType": "SceneItemsNode", + "items": [ + { + "id": "b0253367-7eda-42a9-8758-f38fa038de84", + "sourceId": "image_source_f0ec48b4-a492-4706-9f3f-1320e44516d0", + "x": 0, + "y": 0, + "scaleX": 1, + "scaleY": 1, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "72e5b99b-75dd-47b5-957d-0698021426a8", + "sourceId": "dshow_input_a1a3e9c4-493b-4b96-a217-19c37f014628", + "x": 268.6504004970529, + "y": 169.9474397544256, + "scaleX": 0.5813714295309904, + "scaleY": 0.5813714295309904, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "dad6e13e-5191-4e41-841f-dc4812113b2c", + "sourceId": "image_source_9c0f1fc5-e10b-4117-ad05-817c44e6a904", + "x": 22.161290322580648, + "y": 42.34605736721562, + "scaleX": 0.9008035753808822, + "scaleY": 0.9008035753808822, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "463c8340-7bc5-40c9-949f-ad2d9f2fbd77", + "sourceId": "text_gdiplus_487b9fe0-6a73-4eda-b3ad-31110e26d8e0", + "x": 534, + "y": 644.9173877140703, + "scaleX": 1, + "scaleY": 1, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "6a8c43db-cbd0-405a-bb41-6199f7f4848c", + "sourceId": "image_source_7f92d474-3887-48a7-a96b-6d3cde692f81", + "x": 475, + "y": 651.5081206496519, + "scaleX": 1, + "scaleY": 1, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "45b1b921-8f8e-44d8-ad22-53c5eb7fe51e", + "name": "Twitter", + "sceneNodeType": "folder", + "sceneId": "scene_d8929ac0-3632-4225-b0ba-59a67227b388", + "resourceId": "SceneItemFolder[\"scene_d8929ac0-3632-4225-b0ba-59a67227b388\",\"45b1b921-8f8e-44d8-ad22-53c5eb7fe51e\"]", + "parentId": "", + "childrenIds": [ + "6a8c43db-cbd0-405a-bb41-6199f7f4848c", + "463c8340-7bc5-40c9-949f-ad2d9f2fbd77" + ] + }, + { + "id": "7ca865d8-6c31-4e0a-830d-86a52975d1d7", + "sourceId": "image_source_452bc18f-fcc6-4ca6-b70c-c8126fbc8c5a", + "x": 776.9328474752733, + "y": 39.564195753233776, + "scaleX": 1.06752910164254, + "scaleY": 1.06752910164254, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "8e462f67-86ba-4957-9dba-b7934593314f", + "sourceId": "text_gdiplus_63fcf6f6-7648-404e-bdbf-8bef1d393e8c", + "x": 862.713170223842, + "y": 187.7596903429955, + "scaleX": 1, + "scaleY": 1, + "visible": true, + "crop": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0 + }, + "locked": false, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "rotation": 0 + }, + { + "id": "4a1cf838-f714-4235-afb8-281f7ee032ac", + "name": "一言メッセージ", + "sceneNodeType": "folder", + "sceneId": "scene_d8929ac0-3632-4225-b0ba-59a67227b388", + "resourceId": "SceneItemFolder[\"scene_d8929ac0-3632-4225-b0ba-59a67227b388\",\"4a1cf838-f714-4235-afb8-281f7ee032ac\"]", + "parentId": "", + "childrenIds": [ + "8e462f67-86ba-4957-9dba-b7934593314f", + "7ca865d8-6c31-4e0a-830d-86a52975d1d7" + ] + } + ] + }, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + }, + "filters": { + "schemaVersion": 1, + "nodeType": "SceneFiltersNode", + "items": [] + }, + "active": true + } + ] + }, + "transition": { + "schemaVersion": 1, + "nodeType": "TransitionNode", + "type": "cut_transition", + "duration": 300, + "settings": {}, + "propertiesManagerSettings": { + "mediaBackup": {} + } + }, + "hotkeys": { + "schemaVersion": 2, + "nodeType": "HotkeysNode", + "items": [] + } +} diff --git a/scene-presets/basic/images/nair-preset-background.png b/scene-presets/basic/images/nair-preset-background.png new file mode 100644 index 000000000000..3d1b12a985cf Binary files /dev/null and b/scene-presets/basic/images/nair-preset-background.png differ diff --git a/scene-presets/basic/images/nair-preset-bilibili.png b/scene-presets/basic/images/nair-preset-bilibili.png new file mode 100644 index 000000000000..ff4cfefc6c0c Binary files /dev/null and b/scene-presets/basic/images/nair-preset-bilibili.png differ diff --git a/scene-presets/basic/images/nair-preset-fukidashi.png b/scene-presets/basic/images/nair-preset-fukidashi.png new file mode 100644 index 000000000000..32f099a844c4 Binary files /dev/null and b/scene-presets/basic/images/nair-preset-fukidashi.png differ diff --git a/scene-presets/basic/images/nair-preset-twitter.png b/scene-presets/basic/images/nair-preset-twitter.png new file mode 100644 index 000000000000..53f3115a280d Binary files /dev/null and b/scene-presets/basic/images/nair-preset-twitter.png differ diff --git a/test/api/audio.ts b/test/api/audio.ts index ead0f49289aa..9055d5731b73 100644 --- a/test/api/audio.ts +++ b/test/api/audio.ts @@ -12,7 +12,7 @@ test('The default sources exists', async t => { const audioService = client.getResource('AudioService'); const audioSources = audioService.getSourcesForCurrentScene(); - t.is(audioSources.length, 2); + t.deepEqual(audioSources.map(i => i.getModel().name), ['デスクトップ音声', 'マイク', 'Webカメラ']); }); @@ -26,7 +26,7 @@ test('The sources with audio have to be appeared in AudioService', async t => { scene.createAndAddSource('MyAudio', 'wasapi_output_capture'); const audioSources = audioService.getSourcesForCurrentScene(); - t.is(audioSources.length, 3); + t.deepEqual(audioSources.map(i => i.getModel().name), ['デスクトップ音声', 'マイク', 'MyAudio', 'Webカメラ']); }); diff --git a/test/api/scenes.ts b/test/api/scenes.ts index 68cb13936cae..d69fe70a3abf 100644 --- a/test/api/scenes.ts +++ b/test/api/scenes.ts @@ -2,7 +2,9 @@ import test from 'ava'; import { useSpectron } from '../helpers/spectron'; import { getClient } from '../helpers/api-client'; import { IScenesServiceApi } from '../../app/services/scenes/scenes-api'; +import { sleep } from '../helpers/sleep'; import { SceneBuilder } from '../helpers/scene-builder'; +import { DefaultSceneName } from '../helpers/spectron/scenes'; const path = require('path'); useSpectron({ restartAppAfterEachTest: false }); @@ -21,29 +23,33 @@ test('The default scene exists', async t => { test('Creating, fetching and removing scenes', async t => { const client = await getClient(); const scenesService = client.getResource('ScenesService'); + t.is(scenesService.getScenes().length, 1); + const scene1name = scenesService.getScenes()[0].name; const scene2 = scenesService.createScene('Scene2'); t.is(scene2.name, 'Scene2'); - let scenes = scenesService.getScenes(); + let scenes = scenesService.getScenes().filter(scene => [scene1name, 'Scene2'].includes(scene.name)); let scenesNames = scenes.map(scene => scene.name); - t.deepEqual(scenesNames, ['Scene', 'Scene2']); + t.deepEqual(scenesNames, [scene1name, 'Scene2']); scenesService.removeScene(scenes[1].id); - scenes = scenesService.getScenes(); + scenes = scenesService.getScenes().filter(scene => [scene1name, 'Scene2'].includes(scene.name)); scenesNames = scenes.map(scene => scene.name); - t.deepEqual(scenesNames, ['Scene']); + t.deepEqual(scenesNames, [scene1name]); }); test('Switching between scenes', async t => { const client = await getClient(); const scenesService = client.getResource('ScenesService'); + t.is(scenesService.getScenes().length, 1); + const scene1name = scenesService.getScenes()[0].name; - const scene = scenesService.getScenes().find(scene => scene.name == 'Scene'); + const scene = scenesService.getSceneByName(scene1name); const scene2 = scenesService.createScene('Scene2'); @@ -64,18 +70,19 @@ test('Creating, fetching and removing scene-items', async t => { const client = await getClient(); const scenesService = client.getResource('ScenesService'); - const scene = scenesService.getScenes().find(scene => scene.name == 'Scene'); + const scene = scenesService.getSceneByName(DefaultSceneName); + const presetItemIds = scene.getItems().map(item => item.id); const image1 = scene.createAndAddSource('Image1', 'image_source'); const image2 = scene.createAndAddSource('Image2', 'image_source'); t.is(image1['name'], 'Image1'); - let items = scene.getItems(); + let items = scene.getItems().filter(i => !presetItemIds.includes(i.id)); let itemsNames = items.map(item => item['name']); t.deepEqual(itemsNames, ['Image2', 'Image1']); scene.removeItem(image2.sceneItemId); - items = scene.getItems(); + items = scene.getItems().filter(i => !presetItemIds.includes(i.id)); itemsNames = items.map(item => item['name']); t.deepEqual(itemsNames, ['Image1']); @@ -246,4 +253,4 @@ test('SceneItem.addFile()', async t => { `)); -}); \ No newline at end of file +}); diff --git a/test/api/selection.ts b/test/api/selection.ts index d8606efa41b0..fc7db6d7f0a3 100644 --- a/test/api/selection.ts +++ b/test/api/selection.ts @@ -4,8 +4,8 @@ import { getClient } from '../helpers/api-client'; import { IScenesServiceApi } from '../../app/services/scenes/scenes-api'; import { ISelectionServiceApi } from '../../app/services/selection'; import { ICustomizationServiceApi } from '../../app/services/customization'; -import { SceneBuilder } from "../helpers/scene-builder"; -import { ISceneApi, ISceneNodeApi } from "../../app/services/scenes"; +import { SceneBuilder } from '../helpers/scene-builder'; +import { ISceneApi, ISceneNodeApi } from '../../app/services/scenes'; useSpectron({ restartAppAfterEachTest: false, afterStartCb: afterStart }); @@ -29,6 +29,9 @@ test('Selection', async t => { const scenesService = client.getResource('ScenesService'); const selection = client.getResource('SelectionService'); const scene = scenesService.activeScene; + selection.selectAll(); + const numPresetItems = selection.getSize(); + selection.reset(); const color1 = scene.createAndAddSource('Color1', 'color_source'); const color2 = scene.createAndAddSource('Color2', 'color_source'); @@ -64,7 +67,7 @@ test('Selection', async t => { selection.selectAll(); - t.is(selection.getSize(), 3); + t.is(selection.getSize(), numPresetItems + 3); }); diff --git a/test/api/sources.ts b/test/api/sources.ts index d164ab9ec625..07be4011c12a 100644 --- a/test/api/sources.ts +++ b/test/api/sources.ts @@ -13,6 +13,7 @@ test('Creating, fetching and removing sources', async t => { const scenesService = client.getResource('ScenesService'); const sourcesService = client.getResource('SourcesService'); const scene = scenesService.activeScene; + const presetSceneItemNames = scene.getItems().map(item => item['name']); const colorSource1 = sourcesService.createSource('MyColorSource1', 'color_source'); const colorItem2 = scene.createAndAddSource('MyColorSource2', 'color_source'); @@ -23,7 +24,8 @@ test('Creating, fetching and removing sources', async t => { t.truthy(sources.find(source => source.name === 'MyColorSource2')); const colorItem1 = scene.addSource(colorSource1.sourceId); - let sceneItemNames = scene.getItems().map(item => item['name']); + let sceneItemNames = scene.getItems().map(item => item['name']) + .filter(i => !presetSceneItemNames.includes(i)); t.deepEqual(sceneItemNames, ['MyColorSource1', 'MyColorSource2']); @@ -31,11 +33,10 @@ test('Creating, fetching and removing sources', async t => { colorItem2.remove(); sceneItemNames = scene.getItems().map(item => item['name']); - t.deepEqual(sceneItemNames, []); + t.deepEqual(sceneItemNames.filter(i => !presetSceneItemNames.includes(i)), []); }); - test('Source events', async t => { const client = await getClient(); const scenesService = client.getResource('ScenesService'); diff --git a/test/e2e/scenes.js b/test/e2e/scenes.js index 52839ca26e1e..292a3efafc81 100644 --- a/test/e2e/scenes.js +++ b/test/e2e/scenes.js @@ -7,7 +7,8 @@ import { selectScene, openRenameWindow, openDuplicateWindow, - sceneIsExisting + sceneIsExisting, + DefaultSceneName } from '../helpers/spectron/scenes'; import { getClient } from '../helpers/api-client'; @@ -25,7 +26,7 @@ async function checkDefaultSources(t) { test('The default scene', async t => { const app = t.context.app; await focusMain(t); - t.true(await sceneIsExisting(t, 'Scene')); + t.true(await sceneIsExisting(t, DefaultSceneName)); await checkDefaultSources(t); }); @@ -63,7 +64,7 @@ test('Scene switching with sources', async t => { t.false(await sourceIsExisting(t, sourceName)); // Switch back to the default scene - await selectScene(t, 'Scene'); + await selectScene(t, DefaultSceneName); t.true(await sourceIsExisting(t, sourceName)); }); @@ -91,7 +92,7 @@ test('Rename scene', async t => { const app = t.context.app; const newSceneName = 'Scene2'; - await openRenameWindow(t, 'Scene'); + await openRenameWindow(t, DefaultSceneName); await app.client.setValue('input', newSceneName); await app.client.click('[data-test="Done"]'); diff --git a/test/helpers/api-client.ts b/test/helpers/api-client.ts index f432a6760087..c0ea74c0810e 100644 --- a/test/helpers/api-client.ts +++ b/test/helpers/api-client.ts @@ -1,6 +1,6 @@ import { IJsonRpcRequest } from '../../app/services/jsonrpc'; -import { Subject } from 'rxjs/Subject'; - +import { Subject } from 'rxjs'; +import { first } from 'rxjs/operators'; const net = require('net'); const { spawnSync } = require('child_process'); @@ -285,7 +285,7 @@ export class ApiClient { fetchNextEvent(): Promise { return new Promise((resolve, reject) => { - this.eventReceived.first().subscribe(event => resolve(event)); + this.eventReceived.pipe(first()).subscribe(event => resolve(event)); setTimeout(() => reject('Promise timeout'), PROMISE_TIMEOUT); }); } diff --git a/test/helpers/spectron/index.ts b/test/helpers/spectron/index.ts index 0b352111cd5d..5e09d29c9dae 100644 --- a/test/helpers/spectron/index.ts +++ b/test/helpers/spectron/index.ts @@ -1,5 +1,4 @@ /// -import 'rxjs/add/operator/first'; import test from 'ava'; import { Application } from 'spectron'; import { getClient } from '../api-client'; diff --git a/test/helpers/spectron/scenes.js b/test/helpers/spectron/scenes.js index 5684cd7d682d..591910220e98 100644 --- a/test/helpers/spectron/scenes.js +++ b/test/helpers/spectron/scenes.js @@ -3,6 +3,8 @@ import { focusMain, focusChild } from '.'; import { contextMenuClick } from './context-menu'; import { dialogDismiss } from './dialog'; +export const DefaultSceneName = 'プリセット'; + async function clickSceneAction(t, selector) { await t.context.app.client .$('[data-test="SceneSelector"]') diff --git a/yarn.lock b/yarn.lock index 59cb3bed442a..e98a64750ad6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11478,13 +11478,6 @@ rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" -rxjs@^5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f" - integrity sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A== - dependencies: - symbol-observable "^1.0.1" - rxjs@^6.3.3: version "6.5.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" @@ -11499,6 +11492,13 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" +rxjs@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" + integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== + dependencies: + tslib "^1.9.0" + safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -12382,16 +12382,16 @@ symbol-observable@^0.2.2: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" integrity sha1-lag9smGG1q9+ehjb2XYKL4bQj0A= -symbol-observable@^1.0.1, symbol-observable@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - symbol-observable@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" integrity sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0= +symbol-observable@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -12774,10 +12774,10 @@ ts-jest@^24.0.2: semver "^5.5" yargs-parser "10.x" -ts-loader@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.0.1.tgz#abe8ba59356da8cbc85c01ab5feb85ae998db315" - integrity sha512-9H5ErTIw5t73sdSoFE0hX0RO45B7cdDA4pW1VIQ2wNFAhxSpZcAlv2fwMcfv6SAYLoI7uGwHuzC5dECzmzqtzA== +ts-loader@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" + integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -12916,10 +12916,10 @@ typescript@2.4.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" integrity sha1-w8yxbdqgsjFN4DHn5v7onlujRrw= -typescript@^3.4.5: - version "3.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" - integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== +typescript@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== uglify-js@^2.6: version "2.8.29"