Merge remote-tracking branch 'upstream/skynet' into SEARCH-197

# Conflicts:
#	js/main.js
This commit is contained in:
Keerthi Niranjan 2017-11-07 15:00:14 +05:30
commit 1bbb1f3ff0
75 changed files with 2210 additions and 875 deletions

3
.gitignore vendored
View File

@ -26,4 +26,5 @@ installer/mac/build/
installer/mac/SymphonySettingsPlugin/SymphonySettingsPlugin.xcodeproj/xcuserdata
installer/mac/SymphonySettingsPlugin/SymphonySettingsPlugin.xcodeproj/project.xcworkspace/xcuserdata
installer/win/Symphony-x64-cache
installer/win/Symphony-x64-SetupFiles
installer/win/Symphony-x64-SetupFiles
package-lock.json

View File

@ -57,5 +57,11 @@ In order to achieve those goals Symphony is participating and working in close c
- code coverage reports are placed in dir: converage
- tests are located in dir: tests
## Logging
- Local logging is enabled for dev environments using the module [electron-log](https://www.npmjs.com/package/electron-log)
- On macOS, the logs are stored under `~/Library/Logs/<app name>/log.log`
- On Windows, the logs are stored under `%USERPROFILE%\AppData\Roaming\<app name>\log.log`
- Remote logging is enabled for local and production cases and are sent to the backend server via the remote objects
## Misc notes
If desiring to run against server without proper cert use cmd line option: --ignore-certificate-errors

View File

@ -6,5 +6,10 @@
"notificationSettings": {
"position": "upper-right",
"display": ""
},
"crashReporter": {
"submitURL": "https://localhost:1127/post",
"companyName": "Symphony",
"uploadToServer": false
}
}

View File

@ -16,7 +16,7 @@
</p>
<p>
<label for='image'>image url:</label>
<input type='text' id='image' value='https://lh3.googleusercontent.com/-s2PXL6wWMCc/AAAAAAAAAAI/AAAAAAAAAAA/AAomvV2gUNMMeFsOijwVVpihfg_anpKWQA/s32-c-mo/photo.jpg'/>
<input type='text' id='image' value='https://avatars0.githubusercontent.com/u/13243259?v=4&s=460'/>
</p>
<p>
<label for='flash'>flash:</label>
@ -37,6 +37,13 @@
<button id='open-config-win'>Open configure window</button>
<br>
<hr>
<p>
Crash Process:
<p>
<button id='crash'>Crash Renderer</button>
</p>
<br>
<hr>
<p>Badge Count:<p>
<button id='inc-badge'>increment badge count</button>
<br>
@ -172,6 +179,12 @@
console.log('bounds changed for=', arg)
}
// crash the renderer process
const crash = document.getElementById('crash');
crash.addEventListener('click', function () {
ssf.crashRendererProcess();
});
var getSources = document.getElementById('get-sources');
getSources.addEventListener('click', function() {
ssf.getMediaSources({types: ['window', 'screen']}, function(error, sources) {

View File

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16F73</string>
<string>17A405</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
@ -27,17 +27,17 @@
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>8E3004b</string>
<string>9A1004</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>16E185</string>
<string>17A360</string>
<key>DTSDKName</key>
<string>macosx10.12</string>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0833</string>
<string>0901</string>
<key>DTXcodeBuild</key>
<string>8E3004b</string>
<string>9A1004</string>
<key>InstallerSectionTitle</key>
<string>Pod Settings</string>
<key>NSHumanReadableCopyright</key>

View File

@ -6,7 +6,7 @@
<dict>
<key>Resources/Base.lproj/MyInstallerPane.nib</key>
<data>
5MlJroM8UDQ/u/OkMCiK9J3aK/o=
TF/AqkGdS25ttnHMS1l76ES81/w=
</data>
<key>Resources/InstallerSections.plist</key>
<data>
@ -37,11 +37,11 @@
<dict>
<key>hash</key>
<data>
5MlJroM8UDQ/u/OkMCiK9J3aK/o=
TF/AqkGdS25ttnHMS1l76ES81/w=
</data>
<key>hash2</key>
<data>
KL0ObYrvW43xgEFaDXdRTINfN6qOdiK1EqrvpTusCao=
gxXMI4SoTYE7jYkP5QJ7i804TUXR4x8LGSh99n9qers=
</data>
</dict>
<key>Resources/InstallerSections.plist</key>

View File

@ -92,7 +92,7 @@
3A10EBC71ED4336D0083702F /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = Symphony;
TargetAttributes = {
3A10EBCE1ED4336D0083702F = {
@ -183,7 +183,9 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
@ -191,7 +193,11 @@
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@ -231,7 +237,9 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
@ -239,7 +247,11 @@
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@ -36,6 +37,7 @@
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -1,14 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13196" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13196"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="InstallerSection">
<connections>
<outlet property="firstPane" destination="Qsn-FY-4qK" id="Dhh-1H-QYh"/>
<outlet property="podUrlAlertTextBox" destination="wzk-BB-itI" id="QhW-ay-Yma"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
@ -20,6 +21,7 @@
<outlet property="contentView" destination="TUK-W2-vig" id="gTd-A7-dof"/>
<outlet property="minimizeOnCloseCheckBox" destination="XPe-yO-v9Y" id="ewr-3C-eNJ"/>
<outlet property="parentSection" destination="-2" id="FB7-UV-e8k"/>
<outlet property="podUrlAlertTextBox" destination="wzk-BB-itI" id="Kab-Hu-5vF"/>
<outlet property="podUrlTextBox" destination="uwa-xi-M5X" id="vLY-gf-Cu7"/>
</connections>
</customObject>
@ -27,7 +29,7 @@
<rect key="frame" x="0.0" y="0.0" width="418" height="330"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kEO-Mr-GRW">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kEO-Mr-GRW">
<rect key="frame" x="8" y="186" width="182" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Pod Url (This is where Symphony will be installed)" id="iRN-3c-tUz">
@ -36,8 +38,8 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iAw-NP-Tbm">
<rect key="frame" x="8" y="95" width="188" height="28"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iAw-NP-Tbm">
<rect key="frame" x="8" y="75" width="188" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Minimize Symphony on Close" id="z1k-Zj-Big">
<font key="font" metaFont="system"/>
@ -45,8 +47,8 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KOx-3P-dPz">
<rect key="frame" x="8" y="139" width="146" height="28"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KOx-3P-dPz">
<rect key="frame" x="8" y="119" width="146" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Auto-launch Symphony" id="9id-I6-Bxu">
<font key="font" metaFont="system"/>
@ -55,7 +57,7 @@
</textFieldCell>
</textField>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XPe-yO-v9Y">
<rect key="frame" x="209" y="105" width="22" height="18"/>
<rect key="frame" x="194" y="85" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" inset="2" id="uvu-EE-3sp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
@ -63,24 +65,24 @@
</buttonCell>
</button>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zJM-2d-YYz">
<rect key="frame" x="209" y="149" width="22" height="18"/>
<rect key="frame" x="194" y="129" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="o5T-Og-ooq">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uwa-xi-M5X">
<rect key="frame" x="211" y="197" width="192" height="32"/>
<textField toolTip="Ex: https://my.symphony.com" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uwa-xi-M5X">
<rect key="frame" x="196" y="197" width="207" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="left" title="my.symphony.com" placeholderString="Ex: my.symphony.com" drawsBackground="YES" id="5g9-ba-etY">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="left" title="https://corporate.symphony.com" placeholderString="Ex: https://corporate.symphony.com" drawsBackground="YES" id="5g9-ba-etY">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UYr-oC-RgI">
<rect key="frame" x="8" y="51" width="168" height="28"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="UYr-oC-RgI">
<rect key="frame" x="8" y="31" width="168" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Symphony is always on top" id="7pE-fR-PoD">
<font key="font" metaFont="system"/>
@ -89,13 +91,22 @@
</textFieldCell>
</textField>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8EB-K5-hjN">
<rect key="frame" x="209" y="61" width="22" height="18"/>
<rect key="frame" x="194" y="41" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" inset="2" id="chP-mD-3E9">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsExpansionToolTips="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wzk-BB-itI">
<rect key="frame" x="194" y="171" width="211" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="xnq-4j-scy">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="0.92622652202072542" green="0.44039531974578455" blue="0.39870774994839908" alpha="0.84999999999999998" colorSpace="custom" customColorSpace="displayP3"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</customView>
</objects>

View File

@ -7,11 +7,12 @@
#import <InstallerPlugins/InstallerPlugins.h>
@interface MyInstallerPane : InstallerPane
@interface MyInstallerPane : InstallerPane<NSTextFieldDelegate>
@property (weak) IBOutlet NSButton *minimizeOnCloseCheckBox;
@property (weak) IBOutlet NSButton *autoLaunchCheckBox;
@property (weak) IBOutlet NSTextField *podUrlTextBox;
@property (weak) IBOutlet NSButton *alwaysOnTopCheckBox;
@property (weak) IBOutlet NSTextField *podUrlAlertTextBox;
@end

View File

@ -14,21 +14,40 @@
return [[NSBundle bundleForClass:[self class]] localizedStringForKey:@"PaneTitle" value:nil table:nil];
}
- (void)willExitPane:(InstallerSectionDirection)dir {
// Set the default protocol to https
NSString *protocol = @"https://";
- (void)willEnterPane:(InstallerSectionDirection)dir {
// By default, set the value of the error message textbox to an empty string
[_podUrlAlertTextBox setTitleWithMnemonic:@""];
}
- (BOOL)shouldExitPane:(InstallerSectionDirection)dir {
NSString *podUrl = [_podUrlTextBox stringValue];
// If the pod url is empty, by default, set it to my.symphony.com
if ([podUrl length] == 0) {
podUrl = @"my.symphony.com";
// Check if the url contains a protocol, if not, prepend https to it
NSString *prefix = @"https://";
if (![podUrl hasPrefix:prefix]) {
podUrl = [prefix stringByAppendingString:podUrl];
[_podUrlTextBox setStringValue:podUrl];
}
// Create the final url
NSString *finalUrl = [protocol stringByAppendingString: podUrl];
// Now, validate the url against a url regex
NSString *regex = @"^((?:http:\/\/)|(?:https:\/\/))(www.)?((?:[a-zA-Z0-9]+\.[a-z]{3})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?))([\/a-zA-Z0-9\.]*)$";
NSPredicate *podUrlTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
if ([podUrlTest evaluateWithObject:podUrl]) {
return YES;
}
// In case of an invalid url, display the message under the pod url text box
// and don't go to the next screen, hence return NO
[_podUrlAlertTextBox setTitleWithMnemonic:@"Please enter a valid Pod url."];
return NO;
}
- (void)willExitPane:(InstallerSectionDirection)dir {
NSString *podUrl = [_podUrlTextBox stringValue];
// By default, set autoLaunchOnStart to true
NSString *autoLaunchOnStart = @"true";
@ -52,7 +71,7 @@
}
// Create an array with the selected options
NSArray *symSettings = [[NSArray alloc] initWithObjects:finalUrl, minimizeOnClose, autoLaunchOnStart, alwaysOnTop, nil];
NSArray *symSettings = [[NSArray alloc] initWithObjects:podUrl, minimizeOnClose, autoLaunchOnStart, alwaysOnTop, nil];
// Create a string from the array with new-line as the separator
NSString *symSettingsString = [symSettings componentsJoinedByString:@"\n"];

View File

@ -432,6 +432,12 @@
</dict>
<key>PAYLOAD_TYPE</key>
<integer>0</integer>
<key>SHOW_INVISIBLE</key>
<false/>
<key>SPLIT_FORKS</key>
<true/>
<key>TREAT_MISSING_FILES_AS_WARNING</key>
<false/>
<key>VERSION</key>
<integer>4</integer>
</dict>
@ -453,15 +459,27 @@
<integer>1</integer>
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>FOLLOW_SYMBOLIC_LINKS</key>
<false/>
<key>IDENTIFIER</key>
<string>com.symphony.symphony-desktop</string>
<key>LOCATION</key>
<integer>0</integer>
<key>NAME</key>
<string>Symphony</string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>PAYLOAD_SIZE</key>
<integer>-1</integer>
<key>RELOCATABLE</key>
<false/>
<key>USE_HFS+_COMPRESSION</key>
<false/>
<key>VERSION</key>
<string>1.0.1</string>
<string>2.0.0</string>
</dict>
<key>TYPE</key>
<integer>0</integer>
<key>UUID</key>
<string>91776F5A-09FA-4631-A17C-BE8B5C83AF81</string>
</dict>
@ -499,7 +517,7 @@
<integer>3</integer>
</dict>
<key>CUSTOM</key>
<integer>1</integer>
<true/>
<key>SCALING</key>
<integer>1</integer>
</dict>
@ -537,8 +555,6 @@
<dict/>
</dict>
</dict>
<key>INSTALLATION TYPE</key>
<integer>0</integer>
<key>MODE</key>
<integer>1</integer>
</dict>
@ -631,8 +647,6 @@
</dict>
<key>LICENSE</key>
<dict>
<key>KEYWORDS</key>
<dict/>
<key>LOCALIZATIONS</key>
<array/>
<key>MODE</key>
@ -665,10 +679,6 @@
<dict>
<key>LIST</key>
<array/>
<key>POSTINSTALL_PATH</key>
<dict/>
<key>PREINSTALL_PATH</key>
<dict/>
<key>RESOURCES</key>
<array/>
<key>ROOT_VOLUME_ONLY</key>
@ -676,8 +686,6 @@
</dict>
<key>PROJECT_SETTINGS</key>
<dict>
<key>ADVANCED_OPTIONS</key>
<dict/>
<key>BUILD_FORMAT</key>
<integer>0</integer>
<key>BUILD_PATH</key>
@ -857,6 +865,10 @@
</array>
<key>NAME</key>
<string>Symphony</string>
<key>PAYLOAD_ONLY</key>
<false/>
<key>TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING</key>
<false/>
</dict>
</dict>
<key>SHARED_GLOBAL_DATA</key>

View File

@ -10,22 +10,27 @@
<ROW Property="AI_ThemeStyle" Value="default" MsiKey="AI_ThemeStyle"/>
<ROW Property="AI_UNINSTALLER" Value="msiexec.exe"/>
<ROW Property="ALLUSERS" Value="1" MultiBuildValue="DefaultBuild:2"/>
<ROW Property="ALWAYS_ON_TOP_LABEL" Value="false" Type="4"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
<ROW Property="ARPHELPLINK" Value="http://www.symphony.com"/>
<ROW Property="ARPNOMODIFY" MultiBuildValue="DefaultBuild:1"/>
<ROW Property="ARPNOREPAIR" Value="1"/>
<ROW Property="ARPPRODUCTICON" Value="icon.exe" Type="8"/>
<ROW Property="ARPSYSTEMCOMPONENT" Value="1"/>
<ROW Property="ARPURLINFOABOUT" Value="http://www.symphony.com"/>
<ROW Property="AUTO_START" Value="CheckBox"/>
<ROW Property="AUTO_START_LABEL" Value="true" Type="4"/>
<ROW Property="BannerBitmap" Value="banner" MultiBuildValue="DefaultBuild:Banner.jpg" Type="1" MsiKey="BannerBitmap"/>
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="dialog" MultiBuildValue="DefaultBuild:Tabloid.jpg" Type="1" MsiKey="DialogBitmap"/>
<ROW Property="INVALID_POD_URL" Value="valid" Type="4"/>
<ROW Property="MINIMIZE_ON_CLOSE_LABEL" Value="false" Type="4"/>
<ROW Property="Manufacturer" Value="Symphony"/>
<ROW Property="POD_URL" Value="corporate.symphony.com" Type="4"/>
<ROW Property="POD_URL" Value="https://corporate.symphony.com" Type="4"/>
<ROW Property="ProductCode" Value="1033:{F17EDEB4-A15F-46B9-83E9-8652CBC886B2} " Type="16"/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="Symphony-Electron"/>
<ROW Property="ProductVersion" Value="1.0.0" Type="32"/>
<ROW Property="ProductVersion" Value="2.0.0" Type="32"/>
<ROW Property="REBOOT" MultiBuildValue="DefaultBuild:ReallySuppress"/>
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
@ -52,7 +57,6 @@
<ROW Directory="config_Dir" Directory_Parent="APPDIR" DefaultDir="config"/>
<ROW Directory="jobber_Dir" Directory_Parent="vendor_Dir" DefaultDir="jobber"/>
<ROW Directory="lib_Dir" Directory_Parent="spawnrx_Dir" DefaultDir="lib"/>
<ROW Directory="library_Dir" Directory_Parent="APPDIR" DefaultDir="library"/>
<ROW Directory="locales_Dir" Directory_Parent="APPDIR" DefaultDir="locales"/>
<ROW Directory="node_modules_Dir" Directory_Parent="app.asar.unpacked_Dir" DefaultDir="NODE_M~1|node_modules"/>
<ROW Directory="paulcbetts_Dir" Directory_Parent="node_modules_Dir" DefaultDir="@PAULC~1|@paulcbetts"/>
@ -64,7 +68,8 @@
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
<ROW Component="AI_CustomARPName" ComponentId="{2817ACD9-F494-4729-9830-111EF3311CFA}" Directory_="APPDIR" Attributes="4" KeyPath="DisplayName" Options="1"/>
<ROW Component="Jobber.exe" ComponentId="{A24482DB-EF3B-4FCA-8482-C7F034F7816C}" Directory_="jobber_Dir" Attributes="0" KeyPath="Jobber.exe"/>
<ROW Component="AI_DisableModify" ComponentId="{DA4E8013-2F4B-493A-90A8-BD729DA7EE2C}" Directory_="APPDIR" Attributes="260" KeyPath="NoModify" Options="1"/>
<ROW Component="Jobber.exe" ComponentId="{D019D33C-26A6-400F-9C74-D862032D1A5B}" Directory_="jobber_Dir" Attributes="0" KeyPath="Jobber.exe"/>
<ROW Component="PodUrl" ComponentId="{EA80D82D-BC65-4075-A9A8-F53E2B2513CE}" Directory_="APPDIR" Attributes="260" KeyPath="PodUrl"/>
<ROW Component="ProductInformation" ComponentId="{8B92B687-8AE0-4A5C-B6AB-5D1854009CEA}" Directory_="APPDIR" Attributes="4" KeyPath="Version"/>
<ROW Component="ScreenSnippet.exe" ComponentId="{B92951AB-9E69-4970-A3B5-B4E5C32F3477}" Directory_="APPDIR" Attributes="0" KeyPath="ScreenSnippet.exe"/>
@ -72,7 +77,7 @@
<ROW Component="Symphony.config" ComponentId="{644A231D-2C96-4D3D-ADB0-7820DA373499}" Directory_="config_Dir" Attributes="0" KeyPath="Symphony.config_1" Type="0"/>
<ROW Component="Symphony.exe" ComponentId="{853053E4-D96C-42FE-9AF1-52FF1F449FFD}" Directory_="APPDIR" Attributes="256" KeyPath="Symphony.exe"/>
<ROW Component="am.pak" ComponentId="{76F935B8-6077-4C7A-AD1B-77E2DBA856CC}" Directory_="locales_Dir" Attributes="0" KeyPath="am.pak" Type="0"/>
<ROW Component="ambient.d.ts" ComponentId="{DF6F3FD1-B8F8-4758-977E-B395FA0E1F23}" Directory_="src_1_Dir" Attributes="0" KeyPath="ambient.d.ts" Type="0"/>
<ROW Component="ambient.d.ts" ComponentId="{CAFDE1D9-C005-4ED5-A541-E08241C0EE3E}" Directory_="src_1_Dir" Attributes="0" KeyPath="ambient.d.ts" Type="0"/>
<ROW Component="apimswincoreconsolel110.dll" ComponentId="{24C259E2-26D5-4B36-9029-C373504B0DDD}" Directory_="APPDIR" Attributes="256" KeyPath="apimswincoreconsolel110.dll"/>
<ROW Component="apimswincoredatetimel110.dll" ComponentId="{E600038E-64D7-4245-9AFD-7E47970B7D6A}" Directory_="APPDIR" Attributes="256" KeyPath="apimswincoredatetimel110.dll"/>
<ROW Component="apimswincoredebugl110.dll" ComponentId="{4C033829-BD86-4529-BC64-6848E1662B75}" Directory_="APPDIR" Attributes="256" KeyPath="apimswincoredebugl110.dll"/>
@ -115,32 +120,29 @@
<ROW Component="apimswincrtutilityl110.dll" ComponentId="{C2BDE659-5902-481C-91F4-EE4B0291076C}" Directory_="APPDIR" Attributes="256" KeyPath="apimswincrtutilityl110.dll"/>
<ROW Component="appupdate.yml" ComponentId="{F7586760-660A-4C38-8937-138DBEC18D34}" Directory_="resources_Dir" Attributes="0" KeyPath="appupdate.yml" Type="0"/>
<ROW Component="blink_image_resources_200_percent.pak" ComponentId="{56AB17A5-B690-4CBE-A39D-512381AAAFE1}" Directory_="APPDIR" Attributes="0" KeyPath="blink_image_resources_200_percent.pak" Type="0"/>
<ROW Component="cld.node" ComponentId="{1F2DDDA0-B69B-4AF4-A641-59D455B30ACB}" Directory_="Release_Dir" Attributes="256" KeyPath="cld.node" Type="0"/>
<ROW Component="cld.node" ComponentId="{D9F74D7B-B2E4-4ED8-A023-A54A98AE3F96}" Directory_="Release_Dir" Attributes="256" KeyPath="cld.node" Type="0"/>
<ROW Component="d3dcompiler_47.dll" ComponentId="{C7B87C02-3116-43A8-A70B-3592B70E6AC8}" Directory_="APPDIR" Attributes="256" KeyPath="d3dcompiler_47.dll"/>
<ROW Component="ffmpeg.dll" ComponentId="{A1C4A332-3490-44D8-A5C9-9523889B488B}" Directory_="APPDIR" Attributes="256" KeyPath="ffmpeg.dll"/>
<ROW Component="index.d.ts" ComponentId="{FB146550-23F5-45DD-82E4-90609B0C6562}" Directory_="src_Dir" Attributes="0" KeyPath="index.d.ts" Type="0"/>
<ROW Component="indexvalidator.exec" ComponentId="{BCBA8EF8-A2C0-4BB7-82DB-8B3C2AF655B3}" Directory_="library_Dir" Attributes="0" KeyPath="indexvalidator.exec" Type="0"/>
<ROW Component="indexvalidatorx64.exe" ComponentId="{1BCA45C6-A281-4F96-AE91-2050DB312CD9}" Directory_="library_Dir" Attributes="256" KeyPath="indexvalidatorx64.exe"/>
<ROW Component="indexvalidatorx86.exe" ComponentId="{8BE192B9-D6A3-4CD9-83E5-E77634A70648}" Directory_="library_Dir" Attributes="0" KeyPath="indexvalidatorx86.exe"/>
<ROW Component="index.d.ts" ComponentId="{74D261F1-A6C5-49DD-8554-E48337CC04AF}" Directory_="src_Dir" Attributes="0" KeyPath="index.d.ts" Type="0"/>
<ROW Component="index.js" ComponentId="{77C48E00-B684-4E72-ACF4-15DD0253EF43}" Directory_="lib_Dir" Attributes="0" KeyPath="index.js" Type="0"/>
<ROW Component="libEGL.dll" ComponentId="{8EEC76AB-3601-4D11-B13E-32EC2A38C539}" Directory_="APPDIR" Attributes="256" KeyPath="libEGL.dll"/>
<ROW Component="libGLESv2.dll" ComponentId="{0E8B8B21-B4C0-45C9-95D3-637FD93A4EC0}" Directory_="APPDIR" Attributes="256" KeyPath="libGLESv2.dll"/>
<ROW Component="libsymphonysearchx64.dll" ComponentId="{5C72E1E5-9808-4DC1-B864-3E6C3434F80B}" Directory_="library_Dir" Attributes="256" KeyPath="libsymphonysearchx64.dll"/>
<ROW Component="libsymphonysearchx86.dll" ComponentId="{FEA12082-DA6F-467C-8B51-50AAAC318FA9}" Directory_="library_Dir" Attributes="0" KeyPath="libsymphonysearchx86.dll"/>
<ROW Component="msvcp140.dll" ComponentId="{93A6289C-CF23-4BB8-A579-7FDDD1D15591}" Directory_="APPDIR" Attributes="256" KeyPath="msvcp140.dll"/>
<ROW Component="node.dll" ComponentId="{C0972355-339E-438C-94A3-74174DE4C6B6}" Directory_="APPDIR" Attributes="256" KeyPath="node.dll"/>
<ROW Component="npmignore_1" ComponentId="{3367B8BE-13CC-4957-B834-60B1574BCFDB}" Directory_="spawnrx_Dir" Attributes="0" KeyPath="npmignore_1" Type="0"/>
<ROW Component="node_modules" ComponentId="{A4EB33A8-FEA8-40A5-94EF-705EBE64DDC1}" Directory_="node_modules_Dir" Attributes="0"/>
<ROW Component="npmignore" ComponentId="{849CFFE2-EBC3-4430-AC8B-DEE1B66DF589}" Directory_="spawnrx_Dir" Attributes="0" KeyPath="npmignore" Type="0"/>
<ROW Component="ucrtbase.dll" ComponentId="{16D802A3-DAD4-4BF4-AD64-88D6F63F5D1E}" Directory_="APPDIR" Attributes="256" KeyPath="ucrtbase.dll"/>
<ROW Component="vcruntime140.dll" ComponentId="{2542FC82-8D71-4351-8514-2C0D12772ED5}" Directory_="APPDIR" Attributes="256" KeyPath="vcruntime140.dll"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
<ROW Feature="D564007E3BBE4F85950A09B470A7CA65" Title="Visual C++ Redistributable for Visual Studio 2013 x86" Description="Visual C++ Redistributable for Visual Studio 2013 x86" Display="3" Level="1" Attributes="0" Components="AI_CustomARPName"/>
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="AI_CustomARPName Jobber.exe PodUrl ProductInformation ScreenSnippet.exe Symphony Symphony.config Symphony.exe am.pak ambient.d.ts apimswincoreconsolel110.dll apimswincoredatetimel110.dll apimswincoredebugl110.dll apimswincoreerrorhandlingl110.dll apimswincorefilel110.dll apimswincorefilel120.dll apimswincorefilel210.dll apimswincorehandlel110.dll apimswincoreheapl110.dll apimswincoreinterlockedl110.dll apimswincorelibraryloaderl110.dll apimswincorelocalizationl120.dll apimswincorememoryl110.dll apimswincorenamedpipel110.dll apimswincoreprocessenvironmentl110.dll apimswincoreprocessthreadsl110.dll apimswincoreprocessthreadsl111.dll apimswincoreprofilel110.dll apimswincorertlsupportl110.dll apimswincorestringl110.dll apimswincoresynchl110.dll apimswincoresynchl120.dll apimswincoresysinfol110.dll apimswincoretimezonel110.dll apimswincoreutill110.dll apimswincrtconiol110.dll apimswincrtconvertl110.dll apimswincrtenvironmentl110.dll apimswincrtfilesysteml110.dll apimswincrtheapl110.dll apimswincrtlocalel110.dll apimswincrtmathl110.dll apimswincrtmultibytel110.dll apimswincrtprivatel110.dll apimswincrtprocessl110.dll apimswincrtruntimel110.dll apimswincrtstdiol110.dll apimswincrtstringl110.dll apimswincrttimel110.dll apimswincrtutilityl110.dll appupdate.yml blink_image_resources_200_percent.pak cld.node d3dcompiler_47.dll ffmpeg.dll index.d.ts indexvalidator.exec indexvalidatorx64.exe indexvalidatorx86.exe libEGL.dll libGLESv2.dll libsymphonysearchx64.dll libsymphonysearchx86.dll msvcp140.dll node.dll npmignore_1 ucrtbase.dll vcruntime140.dll"/>
<ROW Feature="D564007E3BBE4F85950A09B470A7CA65" Title="Visual C++ Redistributable for Visual Studio 2013 x86" Description="Visual C++ Redistributable for Visual Studio 2013 x86" Display="3" Level="1" Attributes="0"/>
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="AI_CustomARPName AI_DisableModify Jobber.exe PodUrl ProductInformation ScreenSnippet.exe Symphony Symphony.config Symphony.exe am.pak ambient.d.ts apimswincoreconsolel110.dll apimswincoredatetimel110.dll apimswincoredebugl110.dll apimswincoreerrorhandlingl110.dll apimswincorefilel110.dll apimswincorefilel120.dll apimswincorefilel210.dll apimswincorehandlel110.dll apimswincoreheapl110.dll apimswincoreinterlockedl110.dll apimswincorelibraryloaderl110.dll apimswincorelocalizationl120.dll apimswincorememoryl110.dll apimswincorenamedpipel110.dll apimswincoreprocessenvironmentl110.dll apimswincoreprocessthreadsl110.dll apimswincoreprocessthreadsl111.dll apimswincoreprofilel110.dll apimswincorertlsupportl110.dll apimswincorestringl110.dll apimswincoresynchl110.dll apimswincoresynchl120.dll apimswincoresysinfol110.dll apimswincoretimezonel110.dll apimswincoreutill110.dll apimswincrtconiol110.dll apimswincrtconvertl110.dll apimswincrtenvironmentl110.dll apimswincrtfilesysteml110.dll apimswincrtheapl110.dll apimswincrtlocalel110.dll apimswincrtmathl110.dll apimswincrtmultibytel110.dll apimswincrtprivatel110.dll apimswincrtprocessl110.dll apimswincrtruntimel110.dll apimswincrtstdiol110.dll apimswincrtstringl110.dll apimswincrttimel110.dll apimswincrtutilityl110.dll appupdate.yml blink_image_resources_200_percent.pak cld.node d3dcompiler_47.dll ffmpeg.dll index.d.ts index.js libEGL.dll libGLESv2.dll msvcp140.dll node.dll node_modules npmignore ucrtbase.dll vcruntime140.dll"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
<ROW File="CODE_OF_CONDUCT.md" Component_="npmignore_1" FileName="CODE_O~1.MD|CODE_OF_CONDUCT.md" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\CODE_OF_CONDUCT.md" SelfReg="false" NextFile="COPYING"/>
<ROW File="COPYING" Component_="npmignore_1" FileName="COPYING" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\COPYING" SelfReg="false" NextFile="esdoc.json"/>
<ROW File="Jobber.exe" Component_="Jobber.exe" FileName="Jobber.exe" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\vendor\jobber\Jobber.exe" SelfReg="false" NextFile="indexvalidatorx64.exe" DigSign="true"/>
<ROW File="CODE_OF_CONDUCT.md" Component_="npmignore" FileName="CODE_O~1.MD|CODE_OF_CONDUCT.md" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\CODE_OF_CONDUCT.md" SelfReg="false" NextFile="COPYING"/>
<ROW File="COPYING" Component_="npmignore" FileName="COPYING" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\COPYING" SelfReg="false" NextFile="esdoc.json"/>
<ROW File="Jobber.exe" Component_="Jobber.exe" FileName="Jobber.exe" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\vendor\jobber\Jobber.exe" SelfReg="false" NextFile="cld.node" DigSign="true"/>
<ROW File="LICENSE.electron.txt" Component_="blink_image_resources_200_percent.pak" FileName="LICENS~1.TXT|LICENSE.electron.txt" Attributes="0" SourcePath="..\..\dist\win-unpacked\LICENSE.electron.txt" SelfReg="false" NextFile="LICENSES.chromium.html"/>
<ROW File="LICENSES.chromium.html" Component_="blink_image_resources_200_percent.pak" FileName="LICENS~1.HTM|LICENSES.chromium.html" Attributes="0" SourcePath="..\..\dist\win-unpacked\LICENSES.chromium.html" SelfReg="false" NextFile="natives_blob.bin"/>
<ROW File="ScreenSnippet.exe" Component_="ScreenSnippet.exe" FileName="SCREEN~1.EXE|ScreenSnippet.exe" Attributes="0" SourcePath="..\..\node_modules\screen-snippet\bin\Release\ScreenSnippet.exe" SelfReg="false" NextFile="apimswincoreconsolel110.dll" DigSign="true"/>
@ -194,10 +196,10 @@
<ROW File="bg.pak" Component_="am.pak" FileName="bg.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\bg.pak" SelfReg="false" NextFile="bn.pak"/>
<ROW File="blink_image_resources_200_percent.pak" Component_="blink_image_resources_200_percent.pak" FileName="BLINK_~1.PAK|blink_image_resources_200_percent.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\blink_image_resources_200_percent.pak" SelfReg="false" NextFile="content_resources_200_percent.pak"/>
<ROW File="bn.pak" Component_="am.pak" FileName="bn.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\bn.pak" SelfReg="false" NextFile="ca.pak"/>
<ROW File="build.cmd" Component_="npmignore_1" FileName="build.cmd" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\build.cmd" SelfReg="false" NextFile="build.sh"/>
<ROW File="build.sh" Component_="npmignore_1" FileName="build.sh" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\build.sh" SelfReg="false" NextFile="CODE_OF_CONDUCT.md"/>
<ROW File="build.cmd" Component_="npmignore" FileName="build.cmd" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\build.cmd" SelfReg="false" NextFile="build.sh"/>
<ROW File="build.sh" Component_="npmignore" FileName="build.sh" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\build.sh" SelfReg="false" NextFile="CODE_OF_CONDUCT.md"/>
<ROW File="ca.pak" Component_="am.pak" FileName="ca.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\ca.pak" SelfReg="false" NextFile="cs.pak"/>
<ROW File="cld.node" Component_="cld.node" FileName="CLD~1.NOD|cld.node" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\@paulcbetts\cld\build\Release\cld.node" SelfReg="false" NextFile="npmignore_1"/>
<ROW File="cld.node" Component_="cld.node" FileName="CLD~1.NOD|cld.node" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\@paulcbetts\cld\build\Release\cld.node" SelfReg="false"/>
<ROW File="content_resources_200_percent.pak" Component_="blink_image_resources_200_percent.pak" FileName="CONTEN~1.PAK|content_resources_200_percent.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\content_resources_200_percent.pak" SelfReg="false" NextFile="content_shell.pak"/>
<ROW File="content_shell.pak" Component_="blink_image_resources_200_percent.pak" FileName="CONTEN~2.PAK|content_shell.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\content_shell.pak" SelfReg="false" NextFile="d3dcompiler_47.dll"/>
<ROW File="cs.pak" Component_="am.pak" FileName="cs.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\cs.pak" SelfReg="false" NextFile="da.pak"/>
@ -210,7 +212,7 @@
<ROW File="enUS.pak" Component_="am.pak" FileName="en-US.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\en-US.pak" SelfReg="false" NextFile="es.pak"/>
<ROW File="es.pak" Component_="am.pak" FileName="es.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\es.pak" SelfReg="false" NextFile="es419.pak"/>
<ROW File="es419.pak" Component_="am.pak" FileName="es-419.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\es-419.pak" SelfReg="false" NextFile="et.pak"/>
<ROW File="esdoc.json" Component_="npmignore_1" FileName="ESDOC~1.JSO|esdoc.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\esdoc.json" SelfReg="false" NextFile="index.d.ts"/>
<ROW File="esdoc.json" Component_="npmignore" FileName="ESDOC~1.JSO|esdoc.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\esdoc.json" SelfReg="false" NextFile="index.js"/>
<ROW File="et.pak" Component_="am.pak" FileName="et.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\et.pak" SelfReg="false" NextFile="fa.pak"/>
<ROW File="fa.pak" Component_="am.pak" FileName="fa.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\fa.pak" SelfReg="false" NextFile="fakebidi.pak"/>
<ROW File="fakebidi.pak" Component_="am.pak" FileName="FAKE-B~1.PAK|fake-bidi.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\fake-bidi.pak" SelfReg="false" NextFile="fi.pak"/>
@ -225,22 +227,17 @@
<ROW File="hu.pak" Component_="am.pak" FileName="hu.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\hu.pak" SelfReg="false" NextFile="id.pak"/>
<ROW File="icudtl.dat" Component_="blink_image_resources_200_percent.pak" FileName="icudtl.dat" Attributes="0" SourcePath="..\..\dist\win-unpacked\icudtl.dat" SelfReg="false" NextFile="libEGL.dll"/>
<ROW File="id.pak" Component_="am.pak" FileName="id.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\id.pak" SelfReg="false" NextFile="it.pak"/>
<ROW File="index.d.ts" Component_="index.d.ts" FileName="INDEXD~1.TS|index.d.ts" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\src\index.d.ts" SelfReg="false" NextFile="index.js"/>
<ROW File="index.js" Component_="index.d.ts" FileName="index.js" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\src\index.js" SelfReg="false" NextFile="index.js.map"/>
<ROW File="index.js.map" Component_="index.d.ts" FileName="INDEXJ~1.MAP|index.js.map" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\src\index.js.map" SelfReg="false" NextFile="package.json_1"/>
<ROW File="index.d.ts" Component_="index.d.ts" FileName="INDEXD~1.TS|index.d.ts" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\src\index.d.ts" SelfReg="false" NextFile="index.js_1"/>
<ROW File="index.js" Component_="index.js" FileName="index.js" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\index.js" SelfReg="false" NextFile="index.d.ts"/>
<ROW File="index.js.map" Component_="index.d.ts" FileName="INDEXJ~1.MAP|index.js.map" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\src\index.js.map" SelfReg="false" NextFile="package.json"/>
<ROW File="index.js_1" Component_="index.d.ts" FileName="index.js" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\lib\src\index.js" SelfReg="false" NextFile="index.js.map"/>
<ROW File="index.ts" Component_="ambient.d.ts" FileName="index.ts" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\src\index.ts" SelfReg="false" NextFile="tsconfig.json"/>
<ROW File="indexvalidator.exec" Component_="indexvalidator.exec" FileName="INDEXV~3.EXE|indexvalidator.exec" Attributes="0" SourcePath="..\..\library\indexvalidator.exec" SelfReg="false" NextFile="libsymphonysearchx64.dll"/>
<ROW File="indexvalidatorx64.exe" Component_="indexvalidatorx64.exe" FileName="INDEXV~1.EXE|indexvalidator-x64.exe" Attributes="0" SourcePath="..\..\library\indexvalidator-x64.exe" SelfReg="false" NextFile="indexvalidatorx86.exe" DigSign="true"/>
<ROW File="indexvalidatorx86.exe" Component_="indexvalidatorx86.exe" FileName="INDEXV~2.EXE|indexvalidator-x86.exe" Attributes="0" SourcePath="..\..\library\indexvalidator-x86.exe" SelfReg="false" NextFile="indexvalidator.exec" DigSign="true"/>
<ROW File="it.pak" Component_="am.pak" FileName="it.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\it.pak" SelfReg="false" NextFile="ja.pak"/>
<ROW File="ja.pak" Component_="am.pak" FileName="ja.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\ja.pak" SelfReg="false" NextFile="kn.pak"/>
<ROW File="kn.pak" Component_="am.pak" FileName="kn.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\kn.pak" SelfReg="false" NextFile="ko.pak"/>
<ROW File="ko.pak" Component_="am.pak" FileName="ko.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\ko.pak" SelfReg="false" NextFile="lt.pak"/>
<ROW File="libEGL.dll" Component_="libEGL.dll" FileName="libEGL.dll" Attributes="0" SourcePath="..\..\dist\win-unpacked\libEGL.dll" SelfReg="false" NextFile="libGLESv2.dll"/>
<ROW File="libGLESv2.dll" Component_="libGLESv2.dll" FileName="LIBGLE~1.DLL|libGLESv2.dll" Attributes="0" SourcePath="..\..\dist\win-unpacked\libGLESv2.dll" SelfReg="false" NextFile="LICENSE.electron.txt"/>
<ROW File="libsymphonysearch.dylib" Component_="indexvalidator.exec" FileName="LIBSYM~1.DYL|libsymphonysearch.dylib" Attributes="0" SourcePath="..\..\library\libsymphonysearch.dylib" SelfReg="false"/>
<ROW File="libsymphonysearchx64.dll" Component_="libsymphonysearchx64.dll" FileName="LIBSYM~1.DLL|libsymphonysearch-x64.dll" Attributes="0" SourcePath="..\..\library\libsymphonysearch-x64.dll" SelfReg="false" NextFile="libsymphonysearchx86.dll"/>
<ROW File="libsymphonysearchx86.dll" Component_="libsymphonysearchx86.dll" FileName="LIBSYM~2.DLL|libsymphonysearch-x86.dll" Attributes="0" SourcePath="..\..\library\libsymphonysearch-x86.dll" SelfReg="false" NextFile="libsymphonysearch.dylib"/>
<ROW File="lt.pak" Component_="am.pak" FileName="lt.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\lt.pak" SelfReg="false" NextFile="lv.pak"/>
<ROW File="lv.pak" Component_="am.pak" FileName="lv.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\lv.pak" SelfReg="false" NextFile="ml.pak"/>
<ROW File="ml.pak" Component_="am.pak" FileName="ml.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\ml.pak" SelfReg="false" NextFile="mr.pak"/>
@ -251,8 +248,8 @@
<ROW File="nb.pak" Component_="am.pak" FileName="nb.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\nb.pak" SelfReg="false" NextFile="nl.pak"/>
<ROW File="nl.pak" Component_="am.pak" FileName="nl.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\nl.pak" SelfReg="false" NextFile="pl.pak"/>
<ROW File="node.dll" Component_="node.dll" FileName="node.dll" Attributes="0" SourcePath="..\..\dist\win-unpacked\node.dll" SelfReg="false" NextFile="snapshot_blob.bin"/>
<ROW File="npmignore_1" Component_="npmignore_1" FileName="NPMIGN~1|.npmignore" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\.npmignore" SelfReg="false" NextFile="build.cmd"/>
<ROW File="package.json_1" Component_="npmignore_1" FileName="PACKAG~1.JSO|package.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\package.json" SelfReg="false" NextFile="ambient.d.ts"/>
<ROW File="npmignore" Component_="npmignore" FileName="NPMIGN~1|.npmignore" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\.npmignore" SelfReg="false" NextFile="build.cmd"/>
<ROW File="package.json" Component_="npmignore" FileName="PACKAG~1.JSO|package.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\package.json" SelfReg="false" NextFile="ambient.d.ts"/>
<ROW File="pl.pak" Component_="am.pak" FileName="pl.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\pl.pak" SelfReg="false" NextFile="ptBR.pak"/>
<ROW File="ptBR.pak" Component_="am.pak" FileName="pt-BR.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\pt-BR.pak" SelfReg="false" NextFile="ptPT.pak"/>
<ROW File="ptPT.pak" Component_="am.pak" FileName="pt-PT.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\pt-PT.pak" SelfReg="false" NextFile="ro.pak"/>
@ -268,12 +265,12 @@
<ROW File="te.pak" Component_="am.pak" FileName="te.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\te.pak" SelfReg="false" NextFile="th.pak"/>
<ROW File="th.pak" Component_="am.pak" FileName="th.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\th.pak" SelfReg="false" NextFile="tr.pak"/>
<ROW File="tr.pak" Component_="am.pak" FileName="tr.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\tr.pak" SelfReg="false" NextFile="uk.pak"/>
<ROW File="tsconfig.json" Component_="npmignore_1" FileName="TSCONF~1.JSO|tsconfig.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\tsconfig.json" SelfReg="false" NextFile="tslint.json"/>
<ROW File="tslint.json" Component_="npmignore_1" FileName="TSLINT~1.JSO|tslint.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\tslint.json" SelfReg="false" NextFile="Jobber.exe"/>
<ROW File="tsconfig.json" Component_="npmignore" FileName="TSCONF~1.JSO|tsconfig.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\tsconfig.json" SelfReg="false" NextFile="tslint.json"/>
<ROW File="tslint.json" Component_="npmignore" FileName="TSLINT~1.JSO|tslint.json" Attributes="0" SourcePath="..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\spawn-rx\tslint.json" SelfReg="false" NextFile="Jobber.exe"/>
<ROW File="ucrtbase.dll" Component_="ucrtbase.dll" FileName="ucrtbase.dll" Attributes="0" SourcePath="..\..\dist\win-unpacked\ucrtbase.dll" SelfReg="false" NextFile="vcruntime140.dll"/>
<ROW File="ui_resources_200_percent.pak" Component_="blink_image_resources_200_percent.pak" FileName="UI_RES~1.PAK|ui_resources_200_percent.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\ui_resources_200_percent.pak" SelfReg="false" NextFile="views_resources_200_percent.pak"/>
<ROW File="uk.pak" Component_="am.pak" FileName="uk.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\uk.pak" SelfReg="false" NextFile="vi.pak"/>
<ROW File="vcruntime140.dll" Component_="vcruntime140.dll" FileName="VCRUNT~1.DLL|vcruntime140.dll" Attributes="0" SourcePath="..\..\dist\win-unpacked\vcruntime140.dll" SelfReg="false" NextFile="cld.node"/>
<ROW File="vcruntime140.dll" Component_="vcruntime140.dll" FileName="VCRUNT~1.DLL|vcruntime140.dll" Attributes="0" SourcePath="..\..\dist\win-unpacked\vcruntime140.dll" SelfReg="false" NextFile="npmignore"/>
<ROW File="vi.pak" Component_="am.pak" FileName="vi.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\vi.pak" SelfReg="false" NextFile="zhCN.pak"/>
<ROW File="views_resources_200_percent.pak" Component_="blink_image_resources_200_percent.pak" FileName="VIEWS_~1.PAK|views_resources_200_percent.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\views_resources_200_percent.pak" SelfReg="false" NextFile="am.pak"/>
<ROW File="zhCN.pak" Component_="am.pak" FileName="zh-CN.pak" Attributes="0" SourcePath="..\..\dist\win-unpacked\locales\zh-CN.pak" SelfReg="false" NextFile="zhTW.pak"/>
@ -284,6 +281,9 @@
<ROW AliasRowId="MINIMIZE_ON_CLOSE" AliasRowOperation="2" Value="true"/>
<ROW AliasRowId="ALWAYS_ON_TOP" AliasRowOperation="2" Value="true"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.AiPersistentDataComponent">
<ROW PersistentRow="Symphony.config_1" Type="0" Condition="REINSTALL"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="1" Languages="en" InstallationType="4" UseLargeSchema="true" MsiPackageType="x64"/>
<ATTRIBUTE name="CurrentBuild" value="DefaultBuild"/>
@ -310,6 +310,10 @@
<ROW Fragment="VerifyRepairDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\VerifyRepairDlg.aip"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiActionTextComponent">
<ROW Action="AI_AiBackupImmediate" Description="Preparing backup operation" Template="Path: [1]" DescriptionLocId="ActionText.Description.AI_AiBackupImmediate" TemplateLocId="ActionText.Template.AI_AiBackupImmediate"/>
<ROW Action="AI_AiBackupRollback" Description="Rollback backup" Template="Path: [1]" DescriptionLocId="ActionText.Description.AI_AiBackupRollback" TemplateLocId="ActionText.Template.AI_AiBackupRollback"/>
<ROW Action="AI_AiRestoreDeferred" Description="Executing restore operation" Template="Path: [1]" DescriptionLocId="ActionText.Description.AI_AiRestoreDeferred" TemplateLocId="ActionText.Template.AI_AiRestoreDeferred"/>
<ROW Action="AI_AiRestoreRollback" Description="Rollback restore" Template="Path: [1]" DescriptionLocId="ActionText.Description.AI_AiRestoreRollback" TemplateLocId="ActionText.Template.AI_AiRestoreRollback"/>
<ROW Action="AI_TxtUpdaterCommit" Description="Commit text file changes. " Template="Commit text file changes." DescriptionLocId="ActionText.Description.AI_TxtUpdaterCommit" TemplateLocId="ActionText.Template.AI_TxtUpdaterCommit"/>
<ROW Action="AI_TxtUpdaterConfig" Description="Executing text file updates" Template="Updating text file: &quot;[1]&quot;" DescriptionLocId="ActionText.Description.AI_TxtUpdaterConfig" TemplateLocId="ActionText.Template.AI_TxtUpdaterConfig"/>
<ROW Action="AI_TxtUpdaterInstall" Description="Generating actions to configure text files updates" DescriptionLocId="ActionText.Description.AI_TxtUpdaterInstall"/>
@ -317,6 +321,7 @@
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="Banner.jpg" SourcePath="Assets\Banner.jpg"/>
<ROW Name="ResourceCleaner.dll" SourcePath="&lt;AI_CUSTACTS&gt;ResourceCleaner.dll"/>
<ROW Name="Tabloid.jpg" SourcePath="Assets\Tabloid.jpg"/>
<ROW Name="TxtUpdater.dll" SourcePath="&lt;AI_CUSTACTS&gt;TxtUpdater.dll"/>
<ROW Name="aicustact.dll" SourcePath="&lt;AI_CUSTACTS&gt;aicustact.dll"/>
@ -351,7 +356,6 @@
<ROW Dialog_="FatalError" Control="Title" Type="Text" X="132" Y="10" Width="220" Height="47" Attributes="196611" Text="The [ProductName] [Wizard] ended prematurely" TextStyle="VerdanaBold13" Order="800" TextLocId="Control.Text.FatalError#Title" MsiKey="FatalError#Title"/>
<ROW Dialog_="FilesInUse" Control="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" Attributes="1048577" Text="[BannerBitmap]" Order="400" MsiKey="FilesInUse#BannerBitmap"/>
<ROW Dialog_="FilesInUse" Control="Logo" Type="Text" X="4" Y="228" Width="37" Height="12" Attributes="1" Text="Symphony" Order="600" TextLocId="Control.Text.FilesInUse#Logo" MsiKey="FilesInUse#Logo"/>
<ROW Dialog_="FolderDlg" Control="FolderDlgDialogInitializer" Type="DialogInitializer" X="0" Y="0" Width="0" Height="0" Attributes="0" Order="-1" TextLocId="-" HelpLocId="-" ExtDataLocId="-"/>
<ROW Dialog_="FolderDlg" Control="FolderLabel" Type="Text" X="20" Y="92" Width="348" Height="12" Attributes="3" Text="&amp;Folder:" Help="|" Order="200" TextLocId="Control.Text.FolderDlg#FolderLabel" HelpLocId="Control.Help.FolderDlg#FolderLabel" MsiKey="FolderDlg#FolderLabel"/>
<ROW Dialog_="FolderDlg" Control="FolderEdit" Type="PathEdit" X="18" Y="104" Width="252" Height="18" Attributes="7" Property="APPDIR" Help="|" Order="300" HelpLocId="Control.Help.FolderDlg#FolderEdit" MsiKey="FolderDlg#FolderEdit"/>
<ROW Dialog_="FolderDlg" Control="Browse" Type="PushButton" X="276" Y="104" Width="90" Height="18" Attributes="3" Text="[ButtonText_Browse]" Help="|" Order="400" TextLocId="-" HelpLocId="Control.Help.FolderDlg#Browse" MsiKey="FolderDlg#Browse"/>
@ -367,6 +371,7 @@
<ROW Dialog_="FolderDlg" Control="MinimizeOnCloseCheckBox" Type="CheckBox" X="20" Y="199" Width="98" Height="13" Attributes="3" Property="MINIMIZE_ON_CLOSE" Text="Minimize On Close" Order="1500"/>
<ROW Dialog_="FolderDlg" Control="LaunchOnStartupCheckBox" Type="CheckBox" X="172" Y="174" Width="98" Height="13" Attributes="3" Property="AUTO_START" Text="Launch On Startup" Order="1600"/>
<ROW Dialog_="FolderDlg" Control="AlwaysOnTopCheckBox" Type="CheckBox" X="20" Y="174" Width="98" Height="13" Attributes="3" Property="ALWAYS_ON_TOP" Text="Always On Top" Order="1700"/>
<ROW Dialog_="FolderDlg" Control="Edit_1" Type="Edit" X="360" Y="143" Width="2" Height="9" Attributes="2" Property="INVALID_POD_URL" Text="{260}" Order="1800"/>
<ROW Dialog_="InstallTypeDlg" Control="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" Attributes="1048576" Text="[DialogBitmap]" Order="100" MsiKey="InstallTypeDlg#BannerBitmap"/>
<ROW Dialog_="InstallTypeDlg" Control="Bitmap_background" Type="Bitmap" X="0" Y="0" Width="370" Height="234" Attributes="1" Text="[DialogBitmap]" Order="200"/>
<ROW Dialog_="InstallTypeDlg" Control="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Attributes="3" Text="[ButtonText_Next]" Order="300" TextLocId="-" MsiKey="InstallTypeDlg#Next" Options="1"/>
@ -417,7 +422,30 @@
<ROW Dialog_="ProgressDlg" Control="ProgressBar" Type="ProgressBar" X="35" Y="115" Width="300" Height="10" Attributes="65537" Text="Progress done" Order="1100" TextLocId="Control.Text.ProgressDlg#ProgressBar" MsiKey="ProgressDlg#ProgressBar"/>
<ROW Dialog_="ResumeDlg" Control="Title" Type="Text" X="132" Y="10" Width="220" Height="47" Attributes="196611" Text="Resuming the [ProductName] [Wizard]" TextStyle="VerdanaBold13" Order="500" TextLocId="Control.Text.ResumeDlg#Title" MsiKey="ResumeDlg#Title"/>
<ROW Dialog_="ResumeDlg" Control="Description" Type="Text" X="132" Y="61" Width="220" Height="40" Attributes="196611" Text="The [Wizard] will complete the installation of [ProductName] on your computer. Click &quot;Install&quot; to continue or &quot;Cancel&quot; to exit the [Wizard]." Order="600" TextLocId="Control.Text.ResumeDlg#Description" MsiKey="ResumeDlg#Description"/>
<ROW Dialog_="SpawnWaitDialog" Control="Yes" Type="PushButton" X="72" Y="57" Width="56" Height="17" Attributes="3" Text="[ButtonText_Yes]" Order="100" TextLocId="-"/>
<ROW Dialog_="SpawnWaitDialog" Control="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24" Attributes="5242881" Text="[InfoIcon]" Order="200"/>
<ROW Dialog_="SpawnWaitDialog" Control="Text" Type="Text" X="48" Y="15" Width="194" Height="30" Attributes="3" Text="New Spawn(Wait) Dialog" Order="300"/>
<ROW Dialog_="SpawnWaitDialog" Control="No" Type="PushButton" X="132" Y="57" Width="56" Height="17" Attributes="3" Text="[ButtonText_No]" Order="400" TextLocId="-"/>
<ROW Dialog_="UserExit" Control="Title" Type="Text" X="132" Y="10" Width="220" Height="47" Attributes="196611" Text="The [ProductName] [Wizard] was interrupted" TextStyle="VerdanaBold13" Order="500" TextLocId="Control.Text.UserExit#Title" MsiKey="UserExit#Title"/>
<ROW Dialog_="VerifyDlg" Control="TemplateDlgDialogInitializer" Type="DialogInitializer" X="0" Y="0" Width="0" Height="0" Attributes="0" Order="-1" TextLocId="-" HelpLocId="-" ExtDataLocId="-"/>
<ROW Dialog_="VerifyDlg" Control="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Attributes="3" Text="[ButtonText_Next]" Order="100" TextLocId="-" Options="1"/>
<ROW Dialog_="VerifyDlg" Control="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Attributes="3" Text="[ButtonText_Cancel]" Order="200" TextLocId="-" Options="1"/>
<ROW Dialog_="VerifyDlg" Control="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Attributes="3" Text="[ButtonText_Back]" Order="300" TextLocId="-" Options="1"/>
<ROW Dialog_="VerifyDlg" Control="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" Attributes="1048577" Text="[BannerBitmap]" Order="400"/>
<ROW Dialog_="VerifyDlg" Control="BannerLine" Type="Line" X="0" Y="44" Width="372" Height="0" Attributes="1" Order="500"/>
<ROW Dialog_="VerifyDlg" Control="BottomLine" Type="Line" X="5" Y="234" Width="368" Height="0" Attributes="1" Order="600"/>
<ROW Dialog_="VerifyDlg" Control="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Attributes="196611" Text="Verify the information entered" Order="700"/>
<ROW Dialog_="VerifyDlg" Control="Logo" Type="Text" X="4" Y="228" Width="70" Height="12" Attributes="1" Text="Advanced Installer" Order="800"/>
<ROW Dialog_="VerifyDlg" Control="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Attributes="196611" Text="Settings Verification" TextStyle="[DlgTitleFont]" Order="900"/>
<ROW Dialog_="VerifyDlg" Control="Text_1" Type="Text" X="25" Y="88" Width="76" Height="17" Attributes="65539" Property="TEXT_1_PROP" Text="Symphony POD Url : " Order="1000"/>
<ROW Dialog_="VerifyDlg" Control="Text_2" Type="Text" X="110" Y="88" Width="145" Height="17" Attributes="65539" Property="TEXT_2_PROP" Text="[POD_URL]" Order="1100"/>
<ROW Dialog_="VerifyDlg" Control="Text_3" Type="Text" X="25" Y="114" Width="76" Height="13" Attributes="65539" Property="TEXT_3_PROP" Text="Always on Top : " Order="1200"/>
<ROW Dialog_="VerifyDlg" Control="Text_4" Type="Text" X="110" Y="114" Width="145" Height="10" Attributes="65539" Property="ALWAYS_ON_TOP_LABEL" Text="[ALWAYS_ON_TOP_LABEL]" Order="1300"/>
<ROW Dialog_="VerifyDlg" Control="Text_5" Type="Text" X="25" Y="139" Width="76" Height="13" Attributes="65539" Property="TEXT_3_PROP_1" Text="Launch on Startup : " Order="1400"/>
<ROW Dialog_="VerifyDlg" Control="Text_6" Type="Text" X="110" Y="139" Width="145" Height="10" Attributes="65539" Property="AUTO_START_LABEL" Text="[AUTO_START_LABEL]" Order="1500"/>
<ROW Dialog_="VerifyDlg" Control="Text_7" Type="Text" X="25" Y="164" Width="76" Height="13" Attributes="65539" Property="TEXT_3_PROP_1_1" Text="Minimize on Close : " Order="1600"/>
<ROW Dialog_="VerifyDlg" Control="Text_8" Type="Text" X="110" Y="164" Width="145" Height="10" Attributes="65539" Property="MINIMIZE_ON_CLOSE_LABEL" Text="[MINIMIZE_ON_CLOSE_LABEL]" Order="1700"/>
<ROW Dialog_="VerifyDlg" Control="Text_9" Type="Text" X="25" Y="67" Width="321" Height="25" Attributes="65539" Property="TEXT_9_PROP" Text="You seem to have entered an invalid pod url. Please go back to the previous screen and rectify it." Order="1800"/>
<ROW Dialog_="VerifyReadyDlg" Control="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" Attributes="1048577" Text="[BannerBitmap]" Order="300" MsiKey="VerifyReadyDlg#BannerBitmap"/>
<ROW Dialog_="VerifyReadyDlg" Control="Logo" Type="Text" X="5" Y="228" Width="39" Height="12" Attributes="1" Text="Symphony" Order="500" TextLocId="Control.Text.VerifyReadyDlg#Logo" MsiKey="VerifyReadyDlg#Logo"/>
<ROW Dialog_="VerifyReadyDlg" Control="Text" Type="Text" X="25" Y="70" Width="320" Height="21" Attributes="196611" Text="Click &quot;Install&quot; to begin the installation. If you want to review or change any of your installation settings, click &quot;Back&quot;. Click &quot;Cancel&quot; to exit the wizard." Order="700" TextLocId="Control.Text.VerifyReadyDlg#Text" MsiKey="VerifyReadyDlg#Text"/>
@ -430,6 +458,16 @@
<ATTRIBUTE name="DeletedRows" value="ExitDialog#ViewReadmeText@FolderDlg#Logo@InstallTypeDlg#BannerLine@InstallTypeDlg#InstallTypeText@InstallTypeDlg#Logo@InstallTypeDlg#Title@ProgressDlg#Logo@InstallTypeDlg#Description"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
<ROW Dialog_="VerifyDlg" Control_="Next" Action="Disable" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_1" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_2" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_3" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_4" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_5" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_6" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_7" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_8" Action="Hide" Condition="INVALID_POD_URL = &quot;invalid&quot;"/>
<ROW Dialog_="VerifyDlg" Control_="Text_9" Action="Hide" Condition="INVALID_POD_URL = &quot;valid&quot;"/>
<ATTRIBUTE name="DeletedRows" value="ExitDialog#ViewReadmeText#Hide#((NOT AI_INSTALL) AND (NOT AI_PATCH)) OR ((CTRLS &lt;&gt; 1) AND (CTRLS &lt;&gt; 3))"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
@ -451,23 +489,35 @@
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_PATCH" Ordering="199"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="203"/>
<ROW Dialog_="InstallTypeDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="101"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="201"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyDlg" Condition="AI_INSTALL" Ordering="201"/>
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="InstallTypeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="SpawnDialog" Argument="OutOfRbDiskDlg" Condition="AI_INSTALL AND OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST=&quot;P&quot; OR NOT PROMPTROLLBACKCOST)" Ordering="202" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="EnableRollback" Argument="False" Condition="AI_INSTALL AND OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST=&quot;D&quot;" Ordering="203" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="SpawnDialog" Argument="OutOfDiskDlg" Condition="AI_INSTALL AND ( (OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST=&quot;F&quot;) )" Ordering="204" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="FolderDlgDialogInitializer" Event="[AI_ButtonText_Next_Orig]" Argument="[ButtonText_Next]" Condition="AI_INSTALL" Ordering="0" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="FolderDlgDialogInitializer" Event="[ButtonText_Next]" Argument="[[AI_CommitButton]]" Condition="AI_INSTALL" Ordering="1" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="FolderDlgDialogInitializer" Event="[AI_Text_Next_Orig]" Argument="[Text_Next]" Condition="AI_INSTALL" Ordering="2" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="FolderDlgDialogInitializer" Event="[Text_Next]" Argument="[Text_Install]" Condition="AI_INSTALL" Ordering="3" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="Back" Event="[ButtonText_Next]" Argument="[AI_ButtonText_Next_Orig]" Condition="AI_INSTALL" Ordering="2" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="Back" Event="[Text_Next]" Argument="[AI_Text_Next_Orig]" Condition="AI_INSTALL" Ordering="3" Options="2"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="DoAction" Argument="CheckBoxesScript" Condition="AI_INSTALL" Ordering="205"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="DoAction" Argument="PodUrlValidation" Condition="AI_INSTALL" Ordering="215"/>
<ROW Dialog_="SpawnWaitDialog" Control_="No" Event="EndDialog" Argument="Return" Condition="1" Ordering="100"/>
<ROW Dialog_="SpawnWaitDialog" Control_="Yes" Event="EndDialog" Argument="Return" Condition="1" Ordering="100"/>
<ROW Dialog_="VerifyDlg" Control_="Cancel" Event="SpawnDialog" Argument="CancelDlg" Condition="1" Ordering="100"/>
<ROW Dialog_="VerifyDlg" Control_="Next" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyDlg" Control_="Next" Event="SpawnDialog" Argument="OutOfRbDiskDlg" Condition="AI_INSTALL AND OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST=&quot;P&quot; OR NOT PROMPTROLLBACKCOST)" Ordering="2" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="Next" Event="EnableRollback" Argument="False" Condition="AI_INSTALL AND OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST=&quot;D&quot;" Ordering="3" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="Next" Event="SpawnDialog" Argument="OutOfDiskDlg" Condition="AI_INSTALL AND ( (OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST=&quot;F&quot;) )" Ordering="4" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="TemplateDlgDialogInitializer" Event="[AI_ButtonText_Next_Orig]" Argument="[ButtonText_Next]" Condition="AI_INSTALL" Ordering="0" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="TemplateDlgDialogInitializer" Event="[ButtonText_Next]" Argument="[[AI_CommitButton]]" Condition="AI_INSTALL" Ordering="1" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="TemplateDlgDialogInitializer" Event="[AI_Text_Next_Orig]" Argument="[Text_Next]" Condition="AI_INSTALL" Ordering="2" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="TemplateDlgDialogInitializer" Event="[Text_Next]" Argument="[Text_Install]" Condition="AI_INSTALL" Ordering="3" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="Back" Event="[ButtonText_Next]" Argument="[AI_ButtonText_Next_Orig]" Condition="AI_INSTALL" Ordering="2" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="Back" Event="[Text_Next]" Argument="[AI_Text_Next_Orig]" Condition="AI_INSTALL" Ordering="3" Options="2"/>
<ROW Dialog_="VerifyDlg" Control_="Next" Event="DoAction" Argument="SetCheckboxValues" Condition="AI_INSTALL" Ordering="5"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="Symphony_Dir" Component_="Symphony" ManualDelete="false"/>
<ROW Directory_="node_modules_Dir" Component_="node_modules" ManualDelete="false"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_AiBackupCleanup" Type="1" Source="ResourceCleaner.dll" Target="OnAiBackupCleanup" WithoutSeq="true"/>
<ROW Action="AI_AiBackupImmediate" Type="1" Source="ResourceCleaner.dll" Target="OnAiBackupImmediate"/>
<ROW Action="AI_AiBackupRollback" Type="11521" Source="ResourceCleaner.dll" Target="OnAiBackupRollback"/>
<ROW Action="AI_AiRestoreDeferred" Type="11265" Source="ResourceCleaner.dll" Target="OnAiRestoreDeferred"/>
<ROW Action="AI_AiRestoreRollback" Type="11521" Source="ResourceCleaner.dll" Target="OnAiRestoreRollback" WithoutSeq="true"/>
<ROW Action="AI_AuthorSinglePackage" Type="1" Source="aicustact.dll" Target="AI_AuthorSinglePackage" WithoutSeq="true"/>
<ROW Action="AI_DATA_SETTER" Type="51" Source="CustomActionData" Target="Symphony.exe"/>
<ROW Action="AI_DATA_SETTER_2" Type="51" Source="CustomActionData" Target="Symphony.exe"/>
@ -485,21 +535,27 @@
<ROW Action="AI_TxtUpdaterConfig" Type="11265" Source="TxtUpdater.dll" Target="OnTxtUpdaterConfig" WithoutSeq="true"/>
<ROW Action="AI_TxtUpdaterInstall" Type="1" Source="TxtUpdater.dll" Target="OnTxtUpdaterInstall"/>
<ROW Action="AI_TxtUpdaterRollback" Type="11521" Source="TxtUpdater.dll" Target="OnTxtUpdaterRollback" WithoutSeq="true"/>
<ROW Action="CheckBoxesScript" Type="37" Target="Session.Property(&quot;MIN_ON_CLOSE_PROP&quot;) = (Session.Property(&quot;MIN_ON_CLOSE&quot;) === &quot;Checkbox&quot; ? &quot;true&quot; : &quot;false&quot;);" TargetUnformatted="Session.Property(&quot;AUTO_START&quot;) = (Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot; ? &quot;true&quot; : &quot;false&quot;);&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) = (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot; ? &quot;true&quot; : &quot;false&quot;);&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP&quot;) = (Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot; ? &quot;true&quot; : &quot;false&quot;);" WithoutSeq="true"/>
<ROW Action="KillParagon" Type="1" Source="aicustact.dll" Target="StopProcess" Options="1" AdditionalSeq="AI_DATA_SETTER_2"/>
<ROW Action="KillRenderer" Type="1" Source="aicustact.dll" Target="StopProcess" Options="1" AdditionalSeq="AI_DATA_SETTER"/>
<ROW Action="PodUrlValidation" Type="37" Target="Script Text" TargetUnformatted="// First, check if the protocol is part of the url, if not, prepend it&#13;&#10;var prefix = &quot;https://&quot;;&#13;&#10;if (Session.Property(&quot;POD_URL&quot;).substr(0, prefix.length) !== prefix) {&#13;&#10;&#9;Session.Property(&quot;POD_URL&quot;) = prefix + Session.Property(&quot;POD_URL&quot;);&#13;&#10;}&#13;&#10;&#13;&#10;// Check if the entered pod url is valid&#13;&#10;var podUrlRE = /^((?:http:\/\/)|(?:https:\/\/))(www.)?((?:[a-zA-Z0-9]+\.[a-z]{3})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?))([\/a-zA-Z0-9\.]*)$/;&#13;&#10;var podUrlTest = podUrlRE.test(Session.Property(&quot;POD_URL&quot;));&#13;&#10;if (!podUrlTest) {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;invalid&quot;;&#13;&#10;} else {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;valid&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// By default, we set all the values to false and change based on conditions&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;false&quot;; &#13;&#10;Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;false&quot;;&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;false&quot;;&#13;&#10;&#13;&#10;// If always on top is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;ALWAYS_ON_TOP&quot;) &amp;&amp; Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If launch on startup is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) &amp;&amp; Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If minimise on close is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;AUTO_START&quot;) &amp;&amp; Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}" WithoutSeq="true"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[AI_UserProgramFiles][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
<ROW Action="Symphony.exe" Type="1042" Source="Symphony.exe" Target="--install"/>
<ROW Action="SetCheckboxValues" Type="37" Target="Script Text" TargetUnformatted="// Pick the values from the selected checkboxes and set it against the session variables&#13;&#10;Session.Property(&quot;AUTO_START&quot;) = (Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot; ? &quot;true&quot; : &quot;false&quot;);&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) = (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot; ? &quot;true&quot; : &quot;false&quot;);&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP&quot;) = (Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot; ? &quot;true&quot; : &quot;false&quot;);" WithoutSeq="true"/>
<ROW Action="Symphony.exe" Type="1042" Source="Symphony.exe" Target="--install --peruser"/>
<ROW Action="Symphony.exe_All_User" Type="1042" Source="Symphony.exe" Target="--install"/>
<ROW Action="UninstallPreviousVersions" Type="1" Source="aicustact.dll" Target="UninstallPreviousVersions" Options="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDialogComponent">
<ROW Dialog="SpawnWaitDialog" HCentering="50" VCentering="50" Width="260" Height="85" Attributes="3" Title="[ProductName] [Setup]" Control_Default="Yes" Control_Cancel="No"/>
<ROW Dialog="VerifyDlg" HCentering="50" VCentering="50" Width="370" Height="270" Attributes="3" Title="[ProductName] [Setup]" Control_Default="Next" Control_Cancel="Cancel"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="icon.exe" SourcePath="..\..\build\icon.ico" Index="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel &lt;&gt; 5)" Sequence="210"/>
<ROW Action="AI_STORE_LOCATION" Condition="(Not Installed) OR REINSTALL" Sequence="1501"/>
<ROW Action="AI_STORE_LOCATION" Condition="(Not Installed) OR REINSTALL" Sequence="1502"/>
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE=&quot;No&quot; AND (Not Installed)" Sequence="1399"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="53"/>
<ROW Action="AI_GetArpIconPath" Sequence="1401"/>
@ -509,7 +565,11 @@
<ROW Action="AI_DATA_SETTER" Sequence="54"/>
<ROW Action="AI_SETMIXINSTLOCATION" Sequence="749"/>
<ROW Action="AI_TxtUpdaterInstall" Sequence="5101"/>
<ROW Action="Symphony.exe" Condition="( NOT Installed )" Sequence="5935"/>
<ROW Action="Symphony.exe" Condition="( NOT Installed ) AND ( MSIINSTALLPERUSER )" Sequence="5935"/>
<ROW Action="Symphony.exe_All_User" Condition="( NOT Installed ) AND ( ALLUSERS )" Sequence="5936"/>
<ROW Action="AI_AiBackupImmediate" Sequence="1001"/>
<ROW Action="AI_AiBackupRollback" Sequence="1501"/>
<ROW Action="AI_AiRestoreDeferred" Sequence="6599"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_ResolveKnownFolders" Sequence="52"/>
@ -540,6 +600,7 @@
<ROW Registry="HelpTelephone" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="HelpTelephone" Value="[ARPHELPTELEPHONE]" Component_="AI_CustomARPName"/>
<ROW Registry="InstallLocation" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="InstallLocation" Value="[APPDIR]" Component_="AI_CustomARPName"/>
<ROW Registry="ModifyPath" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="ModifyPath" Value="[AI_UNINSTALLER] /I [ProductCode]" Component_="AI_CustomARPName"/>
<ROW Registry="NoModify" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="NoModify" Value="#1" Component_="AI_DisableModify"/>
<ROW Registry="NoRepair" Root="-1" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName] [ProductVersion]" Name="NoRepair" Value="#1" Component_="AI_CustomARPName"/>
<ROW Registry="Path" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="Path" Value="[APPDIR]" Component_="ProductInformation"/>
<ROW Registry="PodUrl" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="PodUrl" Component_="PodUrl"/>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>About Symphony</title>
<style>
html, body {
margin: 0;
height: 100%;
font-family: sans-serif;
}
.name {
flex: 1;
font-size: 1.3em;
padding: 10px;
font-weight: bold;
}
.version-text {
flex: 1;
font-size: 1em;
color: #2f2f2f;
}
.copyright-text {
flex: 1;
padding: 10px;
font-size: 0.6em;
color: #7f7f7f;
}
.content {
text-align: center;
display: flex;
flex-direction: column;
padding-top: 20px
}
.logo {
margin: auto;
}
</style>
</head>
<body>
<div class="content">
<img class="logo" src="symphony-logo.png">
<span id="app-name" class="name">Symphony</span>
<span id="version" class="version-text"></span>
<span id="copyright" class="copyright-text"></span>
</div>
</body>
</html>

105
js/aboutApp/index.js Normal file
View File

@ -0,0 +1,105 @@
'use strict';
const electron = require('electron');
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const buildNumber = require('../../package.json').buildNumber;
let aboutWindow;
let windowConfig = {
width: 350,
height: 260,
show: false,
modal: true,
autoHideMenuBar: true,
titleBarStyle: true,
resizable: false,
webPreferences: {
preload: path.join(__dirname, 'renderer.js'),
sandbox: true,
nodeIntegration: false
}
};
/**
* method to get the HTML template path
* @returns {string}
*/
function getTemplatePath() {
let templatePath = path.join(__dirname, 'about-app.html');
try {
fs.statSync(templatePath).isFile();
} catch (err) {
log.send(logLevels.ERROR, 'about-window: Could not find template ("' + templatePath + '").');
}
return 'file://' + templatePath;
}
/**
* Opens the about application window for a specific window
* @param {String} windowName - name of the window upon
* which this window should show
*/
function openAboutWindow(windowName) {
// This prevents creating multiple instances of the
// about window
if (aboutWindow) {
if (aboutWindow.isMinimized()) {
aboutWindow.restore();
}
aboutWindow.focus();
return;
}
let allWindows = BrowserWindow.getAllWindows();
allWindows = allWindows.find((window) => { return window.winName === windowName });
// if we couldn't find any window matching the window name
// it will render as a new window
if (allWindows) {
windowConfig.parent = allWindows;
}
aboutWindow = new BrowserWindow(windowConfig);
aboutWindow.setVisibleOnAllWorkspaces(true);
aboutWindow.loadURL(getTemplatePath());
// sets the AlwaysOnTop property for the about window
// if the main window's AlwaysOnTop is true
let focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow && focusedWindow.isAlwaysOnTop()) {
aboutWindow.setAlwaysOnTop(true);
}
aboutWindow.once('ready-to-show', () => {
aboutWindow.show();
});
aboutWindow.webContents.on('did-finish-load', () => {
aboutWindow.webContents.send('buildNumber', buildNumber || '0');
});
aboutWindow.on('close', () => {
destroyWindow();
});
aboutWindow.on('closed', () => {
destroyWindow();
});
}
/**
* Destroys a window
*/
function destroyWindow() {
aboutWindow = null;
}
module.exports = {
openAboutWindow: openAboutWindow
};

27
js/aboutApp/renderer.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
const { remote, ipcRenderer } = require('electron');
renderDom();
/**
* Method that renders application data
*/
function renderDom() {
document.addEventListener('DOMContentLoaded', function () {
const applicationName = remote.app.getName() || 'Symphony';
let appName = document.getElementById('app-name');
let copyright = document.getElementById('copyright');
appName.innerHTML = applicationName;
copyright.innerHTML = `Copyright &copy; ${new Date().getFullYear()} ${applicationName}`
});
}
ipcRenderer.on('buildNumber', (event, buildNumber) => {
let versionText = document.getElementById('version');
const version = remote.app.getVersion();
if (versionText) {
versionText.innerHTML = version ? `Version ${version} (${version}.${buildNumber})` : 'N/A';
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -16,7 +16,7 @@ let throttleActivity;
function activityDetection() {
// Get system idle status and idle time from PaulCBetts package
if (systemIdleTime.getIdleTime() < maxIdleTime) {
return {isUserIdle: false, systemIdleTime: systemIdleTime.getIdleTime()};
return { isUserIdle: false, systemIdleTime: systemIdleTime.getIdleTime() };
}
// If idle for more than 4 mins, monitor system idle status every second
@ -65,7 +65,7 @@ function monitorUserActivity() {
function sendActivity() {
let systemActivity = activityDetection();
if (systemActivity && !systemActivity.isUserIdle && systemActivity.systemIdleTime) {
send({systemIdleTime: systemActivity.systemIdleTime});
send({ systemIdleTime: systemActivity.systemIdleTime });
}
}
@ -84,6 +84,11 @@ function send(data) {
}
}
/**
* Set the activity's window
* @param period
* @param win
*/
function setActivityWindow(period, win) {
maxIdleTime = period;
activityWindow = win;
@ -95,6 +100,5 @@ module.exports = {
send: send,
setActivityWindow: setActivityWindow,
activityDetection: activityDetection,
monitorUserActivity: monitorUserActivity, // Exporting this for unit test
initiateActivityDetection: initiateActivityDetection
};
monitorUserActivity: monitorUserActivity, // Exporting this for unit tests
};

View File

@ -10,6 +10,10 @@ const maxCount = 1e8;
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');
/**
* Shows the badge count
* @param count
*/
function show(count) {
if (typeof count !== 'number') {
log.send(logLevels.WARN, 'badgeCount: invalid func arg, must be a number: ' + count);
@ -37,6 +41,11 @@ function show(count) {
}
}
/**
* Sets the data url
* @param dataUrl
* @param count
*/
function setDataUrl(dataUrl, count) {
const mainWindow = windowMgr.getMainWindow();
if (mainWindow && dataUrl && count) {
@ -50,4 +59,4 @@ function setDataUrl(dataUrl, count) {
module.exports = {
show: show,
setDataUrl: setDataUrl
}
};

View File

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Authentication Request</title>
<style>
html, body {
margin: 0;
height: 100%;
font-family: sans-serif;
}
.container {
display: flex;
flex-direction: column;
text-align: center;
padding: 20px
}
span {
padding-top: 10px;
text-align: start;
}
.hostname {
font-size: .9em;
}
form {
padding-top: 15px;
}
table {
border-spacing: 5px;
}
input {
width: 200px;
height: 20px;
display: inline-block;
}
button {
width: 80px;
}
.footer {
display: flex;
text-align: center;
padding-top: 25px;
}
.button-container {
flex: 1;
}
</style>
</head>
<body>
<div class="container">
<span>Please provide your login credentials for:</span>
<span id="hostname" class="hostname">hostname</span>
<form id="basicAuth" name="Basic Auth" action="Login">
<table class="form">
<tbody>
<tr>
<td>User name:</td>
<td>
<input id="username" name="username" title="Username" required>
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<input id="password" type="password" title="Password" required>
</td>
</tr>
</tbody>
</table>
<div class="footer">
<div class="button-container">
<button type="submit" id="login">Log In</button>
</div>
<div class="button-container">
<button id="cancel">Cancel</button>
</div>
</div>
</form>
</div>
</body>
</html>

126
js/basicAuth/index.js Normal file
View File

@ -0,0 +1,126 @@
'use strict';
const electron = require('electron');
const BrowserWindow = electron.BrowserWindow;
const ipc = electron.ipcMain;
const path = require('path');
const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
let basicAuthWindow;
const local = {};
let windowConfig = {
width: 360,
height: 270,
show: false,
modal: true,
autoHideMenuBar: true,
titleBarStyle: true,
resizable: false,
webPreferences: {
preload: path.join(__dirname, 'renderer.js'),
sandbox: true,
nodeIntegration: false
}
};
/**
* method to get the HTML template path
* @returns {string}
*/
function getTemplatePath() {
let templatePath = path.join(__dirname, 'basic-auth.html');
try {
fs.statSync(templatePath).isFile();
} catch (err) {
log.send(logLevels.ERROR, 'basic-auth: Could not find template ("' + templatePath + '").');
}
return 'file://' + templatePath;
}
/**
* Opens the basic auth window for authentication
* @param {String} windowName - name of the window upon which this window should show
* @param {String} hostname - name of the website that requires authentication
* @param {Function} callback
*/
function openBasicAuthWindow(windowName, hostname, callback) {
// Register callback function
if (typeof callback === 'function') {
local.authCallback = callback;
}
// This prevents creating multiple instances of the
// basic auth window
if (basicAuthWindow) {
if (basicAuthWindow.isMinimized()) {
basicAuthWindow.restore();
}
basicAuthWindow.focus();
return;
}
let allWindows = BrowserWindow.getAllWindows();
allWindows = allWindows.find((window) => { return window.winName === windowName });
// if we couldn't find any window matching the window name
// it will render as a new window
if (allWindows) {
windowConfig.parent = allWindows;
}
basicAuthWindow = new BrowserWindow(windowConfig);
basicAuthWindow.setVisibleOnAllWorkspaces(true);
basicAuthWindow.loadURL(getTemplatePath());
// sets the AlwaysOnTop property for the basic auth window
// if the main window's AlwaysOnTop is true
let focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow && focusedWindow.isAlwaysOnTop()) {
basicAuthWindow.setAlwaysOnTop(true);
}
basicAuthWindow.once('ready-to-show', () => {
basicAuthWindow.show();
});
basicAuthWindow.webContents.on('did-finish-load', () => {
basicAuthWindow.webContents.send('hostname', hostname);
});
basicAuthWindow.on('close', () => {
destroyWindow();
});
basicAuthWindow.on('closed', () => {
destroyWindow();
});
}
ipc.on('login', (event, args) => {
if (typeof args === 'object' && typeof local.authCallback === 'function') {
local.authCallback(args.username, args.password);
basicAuthWindow.close();
}
});
ipc.on('close-basic-auth', () => {
if (basicAuthWindow) {
basicAuthWindow.close();
}
});
/**
* Destroys a window
*/
function destroyWindow() {
basicAuthWindow = null;
}
module.exports = {
openBasicAuthWindow: openBasicAuthWindow
};

55
js/basicAuth/renderer.js Normal file
View File

@ -0,0 +1,55 @@
'use strict';
const electron = require('electron');
const ipc = electron.ipcRenderer;
renderDom();
/**
* Method that renders application data
*/
function renderDom() {
document.addEventListener('DOMContentLoaded', function () {
loadContent();
});
}
function loadContent() {
let basicAuth = document.getElementById('basicAuth');
let cancel = document.getElementById('cancel');
if (basicAuth) {
basicAuth.onsubmit = (e) => {
e.preventDefault();
submitForm();
};
}
if (cancel) {
cancel.addEventListener('click', () => {
ipc.send('close-basic-auth');
});
}
}
/**
* Method that gets invoked on submitting the form
*/
function submitForm() {
let username = document.getElementById('username').value;
let password = document.getElementById('password').value;
if (username && password) {
ipc.send('login', { username, password });
}
}
/**
* Updates the hosts name
*/
ipc.on('hostname', (event, host) => {
let hostname = document.getElementById('hostname');
if (hostname){
hostname.innerHTML = host || 'unknown';
}
});

View File

@ -4,16 +4,27 @@ const electron = require('electron');
const app = electron.app;
const path = require('path');
const fs = require('fs');
const AppDirectory = require('appdirectory');
const omit = require('lodash.omit');
const pick = require('lodash.pick');
const difference = require('lodash.difference');
const isDevEnv = require('./utils/misc.js').isDevEnv;
const isMac = require('./utils/misc.js').isMac;
const getRegistry = require('./utils/getRegistry.js');
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');
const configFileName = 'Symphony.config';
const dirs = new AppDirectory('Symphony');
// cached config when first reading files. initially undefined and will be
// updated when read from disk.
let userConfig;
let globalConfig;
let ignoreSettings = ['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'url'];
/**
* Tries to read given field from user config file, if field doesn't exist
* then tries reading from global config. User config is stord in directory:
@ -29,17 +40,22 @@ let globalConfig;
*/
function getConfigField(fieldName) {
return getUserConfigField(fieldName)
.then(function(value) {
// got value from user config
return value;
}, function () {
// failed to get value from user config, so try global config
return getGlobalConfigField(fieldName);
});
.then((value) => {
// got value from user config
return value;
}, () => {
// failed to get value from user config, so try global config
return getGlobalConfigField(fieldName);
});
}
/**
* Gets a specific user config value for a field
* @param fieldName
* @returns {Promise}
*/
function getUserConfigField(fieldName) {
return readUserConfig().then(function(config) {
return readUserConfig().then((config) => {
if (typeof fieldName === 'string' && fieldName in config) {
return config[fieldName];
}
@ -48,16 +64,25 @@ function getUserConfigField(fieldName) {
});
}
function readUserConfig() {
return new Promise(function(resolve, reject) {
/**
* Reads the user config file and returns all the attributes
* @param customConfigPath
* @returns {Promise}
*/
function readUserConfig(customConfigPath) {
return new Promise((resolve, reject) => {
if (userConfig) {
resolve(userConfig);
return;
}
let configPath = path.join(app.getPath('userData'), configFileName);
let configPath = customConfigPath;
fs.readFile(configPath, 'utf8', function(err, data) {
if (!configPath) {
configPath = path.join(app.getPath('userData'), configFileName);
}
fs.readFile(configPath, 'utf8', (err, data) => {
if (err) {
reject('cannot open user config file: ' + configPath + ', error: ' + err);
} else {
@ -74,8 +99,13 @@ function readUserConfig() {
});
}
/**
* Gets a specific user config value for a field
* @param fieldName
* @returns {Promise}
*/
function getGlobalConfigField(fieldName) {
return readGlobalConfig().then(function(config) {
return readGlobalConfig().then((config) => {
if (typeof fieldName === 'string' && fieldName in config) {
return config[fieldName];
}
@ -93,7 +123,7 @@ function getGlobalConfigField(fieldName) {
* installed app). for dev env, the file is read directly from packed asar file.
*/
function readGlobalConfig() {
return new Promise(function(resolve, reject) {
return new Promise((resolve, reject) => {
if (globalConfig) {
resolve(globalConfig);
return;
@ -113,7 +143,7 @@ function readGlobalConfig() {
configPath = path.join(execPath, isMac ? '..' : '', globalConfigFileName);
}
fs.readFile(configPath, 'utf8', function(err, data) {
fs.readFile(configPath, 'utf8', (err, data) => {
if (err) {
reject('cannot open global config file: ' + configPath + ', error: ' + err);
} else {
@ -124,12 +154,12 @@ function readGlobalConfig() {
reject('can not parse config file data: ' + data + ', error: ' + err);
}
getRegistry('PodUrl')
.then(function(url) {
globalConfig.url = url;
resolve(globalConfig);
}).catch(function () {
resolve(globalConfig);
});
.then((url) => {
globalConfig.url = url;
resolve(globalConfig);
}).catch(() => {
resolve(globalConfig);
});
}
});
});
@ -143,21 +173,27 @@ function readGlobalConfig() {
*/
function updateConfigField(fieldName, newValue) {
return readUserConfig()
.then(function(config) {
return saveUserConfig(fieldName, newValue, config);
},
function() {
// in case config doesn't exist, can't read or is corrupted.
// add configVersion - just in case in future we need to provide
// upgrade capabilities.
return saveUserConfig(fieldName, newValue, {
configVersion: '1.0.0'
.then((config) => {
return saveUserConfig(fieldName, newValue, config);
}, () => {
// in case config doesn't exist, can't read or is corrupted.
// add configVersion - just in case in future we need to provide
// upgrade capabilities.
return saveUserConfig(fieldName, newValue, {
configVersion: '1.0.0'
});
});
});
}
/**
* Saves an updated value to the user config
* @param fieldName
* @param newValue
* @param oldConfig
* @returns {Promise}
*/
function saveUserConfig(fieldName, newValue, oldConfig) {
return new Promise(function(resolve, reject) {
return new Promise((resolve, reject) => {
let configPath = path.join(app.getPath('userData'), configFileName);
if (!oldConfig || !fieldName) {
@ -182,17 +218,183 @@ function saveUserConfig(fieldName, newValue, oldConfig) {
});
}
/**
* Updates the existing user config settings by removing
* 'minimizeOnClose', 'launchOnStartup', 'url' and 'alwaysOnTop'
* @param {Object} oldUserConfig the old user config object
*/
function updateUserConfig(oldUserConfig) {
return new Promise((resolve, reject) => {
// create a new object from the old user config
// by ommitting the user related settings from
// the old user config
let newUserConfig = omit(oldUserConfig, ignoreSettings);
let newUserConfigString = JSON.stringify(newUserConfig, null, 2);
// get the user config path
let userConfigFile;
if (isMac) {
userConfigFile = path.join(dirs.userConfig(), configFileName);
} else {
userConfigFile = path.join(app.getPath('userData'), configFileName);
}
if (!userConfigFile) {
return reject('user config file doesn\'t exist');
}
// write the new user config changes to the user config file
fs.writeFileSync(userConfigFile, newUserConfigString, 'utf-8');
return resolve();
});
}
/**
* Manipulates user config on windows
* @param {String} perUserInstall - Is a flag to determine if we are installing for an individual user
* @returns {Promise}
*/
function updateUserConfigWin(perUserInstall) {
return new Promise((resolve, reject) => {
// we get the user config path using electron
const userConfigFile = path.join(app.getPath('userData'), configFileName);
// if it's not a per user installation or if the
// user config file doesn't exist, we simple move on
if (!perUserInstall || !fs.existsSync(userConfigFile)) {
log.send(logLevels.WARN, 'config: Could not find the user config file!');
reject();
return;
}
// In case the file exists, we remove it so that all the
// values are fetched from the global config
// https://perzoinc.atlassian.net/browse/ELECTRON-126
readUserConfig(userConfigFile).then((data) => {
resolve(updateUserConfig(data));
}).catch((err) => {
reject(err);
});
});
}
/**
* Manipulates user config on macOS
* @param {String} globalConfigPath - The global config path from installer
* @returns {Promise}
*/
function updateUserConfigMac() {
return new Promise((resolve, reject) => {
const userConfigFile = path.join(dirs.userConfig(), configFileName);
// if user config file does't exist, just use the global config settings
// i.e. until an user makes changes manually using the menu items
if (!fs.existsSync(userConfigFile)) {
log.send(logLevels.WARN, 'config: Could not find the user config file!');
reject();
return;
}
// In case the file exists, we remove it so that all the
// values are fetched from the global config
// https://perzoinc.atlassian.net/browse/ELECTRON-126
readUserConfig(userConfigFile).then((data) => {
resolve(updateUserConfig(data));
}).catch((err) => {
reject(err);
});
});
}
/**
* Method that tries to grab multiple config field from user config
* if field doesn't exist tries reading from global config
*
* @param {Array} fieldNames - array of config filed names
* @returns {Promise} - object all the config data from user and global config
*/
function getMultipleConfigField(fieldNames) {
return new Promise((resolve, reject) => {
let userConfigData;
if (!fieldNames && fieldNames.length < 0) {
reject('cannot read config file, invalid fields');
return;
}
// reads user config data
readUserConfig().then((config) => {
userConfigData = pick(config, fieldNames);
let userConfigKeys = userConfigData ? Object.keys(userConfigData) : undefined;
/**
* Condition to validate data from user config,
* if all the required fields are not present
* this tries to fetch the remaining fields from global config
*/
if (!userConfigKeys || userConfigKeys.length < fieldNames.length) {
// remainingConfig - config field that are not present in the user config
let remainingConfig = difference(fieldNames, userConfigKeys);
if (remainingConfig && Object.keys(remainingConfig).length > 0) {
readGlobalConfig().then((globalConfigData) => {
// assigns the remaining fields from global config to the user config
userConfigData = Object.assign(userConfigData, pick(globalConfigData, remainingConfig));
resolve(userConfigData);
}).catch((err) => {
reject(err);
});
}
} else {
resolve(userConfigData);
}
}).catch(() => {
// This reads global config if there was any
// error while reading user config
readGlobalConfig().then((config) => {
userConfigData = pick(config, fieldNames);
resolve(userConfigData);
}).catch((err) => {
reject(err);
});
});
});
}
/**
* Clears the cached config
*/
function clearCachedConfigs() {
userConfig = null;
globalConfig = null;
}
module.exports = {
getConfigField,
updateConfigField,
configFileName,
getConfigField,
updateConfigField,
updateUserConfigWin,
updateUserConfigMac,
getMultipleConfigField,
// items below here are only exported for testing, do NOT use!
saveUserConfig,
clearCachedConfigs
};

View File

@ -12,23 +12,32 @@
// renderer process, this will have to do. See github issue posted here to
// electron: https://github.com/electron/electron/issues/9312
var { ipcRenderer } = require('electron');
const { ipcRenderer } = require('electron');
var nextId = 0;
var includes = [].includes;
let nextId = 0;
let includes = [].includes;
function getNextId() {
return ++nextId;
}
// |options.type| can not be empty and has to include 'window' or 'screen'.
/**
* Checks if the options and their types are valid
* @param options |options.type| can not be empty and has to include 'window' or 'screen'.
* @returns {boolean}
*/
function isValid(options) {
return ((options != null ? options.types : undefined) != null) && Array.isArray(options.types);
return ((options !== null ? options.types : undefined) !== null) && Array.isArray(options.types);
}
/**
* Gets the sources for capturing screens / windows
* @param options
* @param callback
* @returns {*}
*/
function getSources(options, callback) {
var captureScreen, captureWindow, id;
let captureScreen, captureWindow, id;
if (!isValid(options)) {
return callback(new Error('Invalid options'));
}
@ -36,33 +45,34 @@ function getSources(options, callback) {
captureScreen = includes.call(options.types, 'screen');
let updatedOptions = options;
if (updatedOptions.thumbnailSize == null) {
if (!updatedOptions.thumbnailSize) {
updatedOptions.thumbnailSize = {
width: 150,
height: 150
}
};
}
id = getNextId();
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, updatedOptions.thumbnailSize, id);
return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function (event, sources) {
var source;
callback(null, (function () {
var i, len, results
return ipcRenderer.once('ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function(event, sources) {
let source;
callback(null, (function() {
let i, len, results;
results = [];
for (i = 0, len = sources.length; i < len; i++) {
source = sources[i]
source = sources[i];
results.push({
id: source.id,
name: source.name,
thumbnail: source.thumbnail
})
});
}
return results
return results;
}()));
});
}
module.exports = getSources;
module.exports = getSources;

View File

@ -0,0 +1,25 @@
'use strict';
const electron = require('electron');
const basicAuth = require('../basicAuth');
/**
* Having a proxy or hosts that requires authentication will allow user to
* enter their credentials 'username' & 'password'
*/
electron.app.on('login', (event, webContents, request, authInfo, callback) => {
event.preventDefault();
// name of the host to display
let hostname = authInfo.host || authInfo.realm;
let browserWin = electron.BrowserWindow.fromWebContents(webContents);
let windowName = browserWin.winName || '';
/**
* Opens an electron modal window in which
* user can enter credentials fot the host
*/
basicAuth.openBasicAuthWindow(windowName, hostname, callback);
});

View File

@ -11,14 +11,15 @@ const logLevels = require('../enums/logLevels.js');
* @param {String} url Url that failed
* @param {String} errorDesc Description of error
* @param {Number} errorCode Error code
* @param {callback} retryCallback Callback when user clicks reload
* @param {function} retryCallback Callback when user clicks reload
* @param {Boolean} showDialog Indicates if a dialog need to be show to a user
*/
function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
function showLoadFailure(win, url, errorDesc, errorCode, retryCallback, showDialog) {
let msg;
if (url) {
msg = 'Error loading URL:\n' + url;
} else {
msg = 'Error loading window'
msg = 'Error loading window';
}
if (errorDesc) {
msg += '\n\n' + errorDesc;
@ -27,18 +28,20 @@ function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
msg += '\n\nError Code: ' + errorCode;
}
electron.dialog.showMessageBox(win, {
type: 'error',
buttons: [ 'Reload', 'Ignore' ],
defaultId: 0,
cancelId: 1,
noLink: true,
title: 'Loading Error',
message: msg
}, response);
if (showDialog) {
electron.dialog.showMessageBox(win, {
type: 'error',
buttons: ['Reload', 'Ignore'],
defaultId: 0,
cancelId: 1,
noLink: true,
title: 'Loading Error',
message: msg
}, response);
}
log.send(logLevels.WARNING, 'Load failure msg: ' + errorDesc +
' errorCode: ' + errorCode + ' for url:' + url);
' errorCode: ' + errorCode + ' for url:' + url);
// async handle of user input
function response(buttonId) {
@ -53,11 +56,11 @@ function showLoadFailure(win, url, errorDesc, errorCode, retryCallback) {
* Show message indicating network connectivity has been lost.
* @param {BrowserWindow} win Window to host dialog
* @param {String} url Url that failed
* @param {callback} retryCallback Callback when user clicks reload
* @param {function} retryCallback Callback when user clicks reload
*/
function showNetworkConnectivityError(win, url, retryCallback) {
var errorDesc = 'Network connectivity has been lost, check your internet connection.';
showLoadFailure(win, url, errorDesc, 0, retryCallback);
let errorDesc = 'Network connectivity has been lost, check your internet connection.';
showLoadFailure(win, url, errorDesc, 0, retryCallback, true);
}
module.exports = { showLoadFailure, showNetworkConnectivityError };
module.exports = { showLoadFailure, showNetworkConnectivityError };

View File

@ -19,12 +19,13 @@ local.ipcRenderer.on('downloadProgress', () => {
/**
* Open file in default app.
* @param id
*/
function openFile(id) {
let fileIndex = local.downloadItems.findIndex((item) => {
return item._id === id
return item._id === id;
});
if (fileIndex !== -1){
if (fileIndex !== -1) {
let openResponse = remote.shell.openExternal(`file:///${local.downloadItems[fileIndex].savedPath}`);
if (!openResponse) {
remote.dialog.showErrorBox("File not found", 'The file you are trying to open cannot be found in the specified path.');
@ -34,10 +35,11 @@ function openFile(id) {
/**
* Show downloaded file in explorer or finder.
* @param id
*/
function showInFinder(id) {
let showFileIndex = local.downloadItems.findIndex((item) => {
return item._id === id
return item._id === id;
});
if (showFileIndex !== -1) {
let showResponse = remote.shell.showItemInFolder(local.downloadItems[showFileIndex].savedPath);
@ -47,12 +49,17 @@ function showInFinder(id) {
}
}
/**
* Create the document object model
* @param arg
*/
function createDOM(arg) {
if (arg && arg._id) {
let fileDisplayName = getFileDisplayName(arg.fileName);
let downloadItemKey = arg._id;
local.downloadItems.push(arg);
let downloadItemKey = arg._id;
let ul = document.getElementById('download-main');
if (ul) {
@ -102,7 +109,8 @@ function createDOM(arg) {
let h2FileName = document.createElement('h2');
h2FileName.classList.add('text-cutoff');
h2FileName.innerHTML = arg.fileName;
h2FileName.innerHTML = fileDisplayName;
h2FileName.title = fileDisplayName;
fileNameDiv.appendChild(h2FileName);
let fileProgressTitle = document.createElement('span');
@ -149,6 +157,9 @@ function createDOM(arg) {
}
}
/**
* Initiate the download manager
*/
function initiate() {
let mainFooter = document.getElementById('footer');
let mainDownloadDiv = document.getElementById('download-manager-footer');
@ -159,7 +170,7 @@ function initiate() {
let ulFind = document.getElementById('download-main');
if (!ulFind){
if (!ulFind) {
let uList = document.createElement('ul');
uList.id = 'download-main';
mainDownloadDiv.appendChild(uList);
@ -167,7 +178,7 @@ function initiate() {
let closeSpanFind = document.getElementById('close-download-bar');
if (!closeSpanFind){
if (!closeSpanFind) {
let closeSpan = document.createElement('span');
closeSpan.id = 'close-download-bar';
closeSpan.classList.add('close-download-bar');
@ -185,4 +196,33 @@ function initiate() {
});
}
}
}
/**
* Return a file display name for the download item
*/
function getFileDisplayName(fileName) {
let fileList = local.downloadItems;
let fileNameCount = 0;
let fileDisplayName = fileName;
/* Check if a file with the same name exists
* (akin to the user downloading a file with the same name again)
* in the download bar
*/
for (let i = 0; i < fileList.length; i++) {
if (fileName === fileList[i].fileName) {
fileNameCount++;
}
}
/* If it exists, add a count to the name like how Chrome does */
if (fileNameCount) {
let extLastIndex = fileDisplayName.lastIndexOf('.');
let fileCount = ' (' + fileNameCount + ')';
fileDisplayName = fileDisplayName.slice(0, extLastIndex) + fileCount + fileDisplayName.slice(extLastIndex);
}
return fileDisplayName;
}

View File

@ -1,7 +1,11 @@
'use strict';
var keyMirror = require('keymirror');
let keyMirror = require('keymirror');
/**
* Set of APIs exposed to the remote object
* @type {Object}
*/
const cmds = keyMirror({
isOnline: null,
registerLogger: null,

View File

@ -1,7 +1,11 @@
'use strict';
var keyMirror = require('keymirror');
let keyMirror = require('keymirror');
/**
* The different log levels
* @type {Object}
*/
module.exports = keyMirror({
ERROR: null,
CONFLICT: null,

View File

@ -1,16 +1,26 @@
'use strict';
const getCmdLineArg = require('./utils/getCmdLineArg.js')
const getCmdLineArg = require('./utils/getCmdLineArg.js');
const { isDevEnv } = require('./utils/misc');
const logLevels = require('./enums/logLevels.js');
const MAX_LOG_QUEUE_LENGTH = 100;
let electronLog;
class Logger {
constructor() {
// browser window that has registered a logger
this.logWindow = null;
// holds log messages received before logger has been registered.
this.logQueue = [];
// Initializes the local logger
if (isDevEnv) {
initializeLocalLogger();
}
}
/**
@ -25,6 +35,10 @@ class Logger {
return;
}
if (isDevEnv) {
logLocally(level, details);
}
let logMsg = {
level: level,
details: details,
@ -45,18 +59,22 @@ class Logger {
}
}
/**
* Sets a window instance for the remote object
* @param win
*/
setLogWindow(win) {
this.logWindow = win;
if (this.logWindow) {
var logMsg = {};
let logMsg = {};
if (Array.isArray(this.logQueue)) {
logMsg.msgs = this.logQueue;
}
// configure desired log level and send pending log msgs
let logLevel = getCmdLineArg(process.argv, '--logLevel=');
let logLevel = getCmdLineArg(process.argv, '--logLevel=', false);
if (logLevel) {
let level = logLevel.split('=')[1];
if (level) {
@ -64,7 +82,7 @@ class Logger {
}
}
if (getCmdLineArg(process.argv, '--enableConsoleLogging')) {
if (getCmdLineArg(process.argv, '--enableConsoleLogging', false)) {
logMsg.showInConsole = true;
}
@ -77,11 +95,40 @@ class Logger {
}
}
var loggerInstance = new Logger();
let loggerInstance = new Logger();
/**
* Initializes the electron logger for local logging
*/
function initializeLocalLogger() {
// eslint-disable-next-line global-require
electronLog = require('electron-log');
electronLog.transports.file.level = 'debug';
electronLog.transports.file.format = '{h}:{i}:{s}:{ms} {text}';
electronLog.transports.file.maxSize = 10 * 1024 * 1024;
electronLog.transports.file.appName = 'Symphony';
}
/**
* Logs locally using the electron-logger
* @param level
* @param message
*/
function logLocally(level, message) {
switch (level) {
case logLevels.ERROR: electronLog.error(message); break;
case logLevels.CONFLICT: electronLog.error(message); break;
case logLevels.WARN: electronLog.warn(message); break;
case logLevels.ACTION: electronLog.warn(message); break;
case logLevels.INFO: electronLog.info(message); break;
case logLevels.DEBUG: electronLog.debug(message); break;
default: electronLog.debug(message);
}
}
// Logger class is only exposed for testing purposes.
module.exports = {
Logger: Logger,
send: loggerInstance.send.bind(loggerInstance),
setLogWindow: loggerInstance.setLogWindow.bind(loggerInstance)
}
};

View File

@ -1,20 +1,23 @@
'use strict';
// Third Party Dependencies
const electron = require('electron');
const app = electron.app;
const crashReporter = electron.crashReporter;
const nodeURL = require('url');
const shellPath = require('shell-path');
const squirrelStartup = require('electron-squirrel-startup');
const AutoLaunch = require('auto-launch');
const urlParser = require('url');
const { getConfigField } = require('./config.js');
// Local Dependencies
const {getConfigField, updateUserConfigWin, updateUserConfigMac} = require('./config.js');
const {setCheckboxValues} = require('./menus/menuTemplate.js');
const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
const getCmdLineArg = require('./utils/getCmdLineArg.js');
const childProcess = require('child_process');
const path = require('path');
const AppDirectory = require('appdirectory');
const dirs = new AppDirectory('Symphony');
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');
const Crypto = require('./cryptoLib');
const crypto = new Crypto();
@ -43,6 +46,23 @@ require('./memoryMonitor.js');
const windowMgr = require('./windowMgr.js');
getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);
function initializeCrashReporter(podUrl) {
getConfigField('crashReporter')
.then((crashReporterConfig) => {
crashReporter.start({companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: {'process': 'main', podUrl: podUrl}});
log.send(logLevels.INFO, 'initialized crash reporter on the main process!');
})
.catch((err) => {
log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the main process. Error is -> ' + err);
});
}
// only allow a single instance of app.
const shouldQuit = app.makeSingleInstance((argv) => {
// Someone tried to run a second instance, we should focus our window.
@ -84,13 +104,20 @@ if (isMac) {
* initialization and is ready to create browser windows.
* Some APIs can only be used after this event occurs.
*/
app.on('ready', setupThenOpenMainWindow);
app.on('ready', readConfigThenOpenMainWindow);
app.on('window-all-closed', function () {
/**
* Is triggered when all the windows are closed
* In which case we quit the app
*/
app.on('window-all-closed', function() {
app.quit();
});
app.on('activate', function () {
/**
* Is triggered when the app is up & running
*/
app.on('activate', function() {
if (windowMgr.isMainWindow(null)) {
setupThenOpenMainWindow();
} else {
@ -109,26 +136,46 @@ app.on('will-quit', function (e) {
// and registry keys in windows
app.setAsDefaultProtocolClient('symphony');
// This event is emitted only on macOS
// at this moment, support for windows
// is in pipeline (https://github.com/electron/electron/pull/8052)
app.on('open-url', function (event, url) {
/**
* This event is emitted only on macOS
* at this moment, support for windows
* is in pipeline (https://github.com/electron/electron/pull/8052)
*/
app.on('open-url', function(event, url) {
handleProtocolAction(url);
});
/**
* Reads the config fields that are required for the menu items
* then opens the main window
*
* This is a workaround for the issue where the menu template was returned
* even before the config data was populated
* https://perzoinc.atlassian.net/browse/ELECTRON-154
*/
function readConfigThenOpenMainWindow() {
setCheckboxValues()
.then(setupThenOpenMainWindow)
.catch(setupThenOpenMainWindow)
}
/**
* Sets up the app (to handle various things like config changes, protocol handling etc.)
* and opens the main window
*/
function setupThenOpenMainWindow() {
processProtocolAction(process.argv);
isAppAlreadyOpen = true;
// allows installer to launch app and set auto startup mode then
// immediately quit.
// allows installer to launch app and set appropriate global / user config params.
let hasInstallFlag = getCmdLineArg(process.argv, '--install', true);
let perUserInstall = getCmdLineArg(process.argv, '--peruser', true);
if (!isMac && hasInstallFlag) {
getConfigField('launchOnStartup')
.then(setStartup)
.then(updateUserConfigWin)
.then(() => updateUserConfigWin(perUserInstall))
.then(app.quit)
.catch(app.quit);
return;
@ -140,8 +187,11 @@ function setupThenOpenMainWindow() {
// as the app is launched as a root user we don't get
// access to the config file
let launchOnStartup = process.argv[3];
updateUserConfigMac()
.then(setStartup(launchOnStartup))
// We wire this in via the post install script
// to get the config file path where the app is installed
let appGlobalConfigPath = process.argv[2];
setStartup(launchOnStartup)
.then(() => updateUserConfigMac(appGlobalConfigPath))
.then(app.quit)
.catch(app.quit);
return;
@ -154,56 +204,34 @@ function setupThenOpenMainWindow() {
electron.screen.on('display-removed', windowMgr.verifyDisplays);
}
function setStartup(lStartup){
/**
* Sets Symphony on startup
* @param lStartup
* @returns {Promise}
*/
function setStartup(lStartup) {
return symphonyAutoLauncher.isEnabled()
.then(function(isEnabled){
if (!isEnabled && lStartup) {
return symphonyAutoLauncher.enable();
}
if (isEnabled && !lStartup) {
return symphonyAutoLauncher.disable();
}
return true;
});
}
// Method to overwrite user config on mac installer
function updateUserConfigMac() {
return new Promise((resolve, reject) => {
let userConfigPath = dirs.userConfig() + '/';
let globalConfigPath = process.argv[2];
let userName = process.env.USER;
childProcess.exec(`rsync -r "${globalConfigPath}" "${userConfigPath}" && chown -R "${userName}" "${userConfigPath}"`, {timeout: 60000}, (err) => {
if (err) {
reject(err);
.then(function(isEnabled) {
if (!isEnabled && lStartup) {
return symphonyAutoLauncher.enable();
}
resolve();
});
});
}
// Method to overwrite user config on windows installer
function updateUserConfigWin() {
return new Promise((resolve, reject) => {
let userConfigPath = app.getPath('userData');
let globalConfigPath = path.join(__dirname, '..', '..', '..', 'config/Symphony.config');
childProcess.exec(`echo D|xcopy /y /e /s /c "${globalConfigPath}" "${userConfigPath}"`, {timeout: 60000}, (err) => {
if (err) {
reject(err);
if (isEnabled && !lStartup) {
return symphonyAutoLauncher.disable();
}
resolve();
return true;
});
});
}
/**
* Checks for the url argument, processes it
* and creates the main window
*/
function getUrlAndCreateMainWindow() {
// for dev env allow passing url argument
if (isDevEnv) {
let url = getCmdLineArg(process.argv, '--url=')
let url = getCmdLineArg(process.argv, '--url=', false);
if (url) {
windowMgr.createMainWindow(url.substr(6));
return;
@ -211,12 +239,16 @@ function getUrlAndCreateMainWindow() {
}
getConfigField('url')
.then(createWin).catch(function (err) {
.then(createWin).catch(function(err) {
let title = 'Error loading configuration';
electron.dialog.showErrorBox(title, title + ': ' + err);
});
}
/**
* Creates a window
* @param urlFromConfig
*/
function createWin(urlFromConfig) {
let protocol = '';
// add https protocol if none found.
@ -224,7 +256,7 @@ function createWin(urlFromConfig) {
if (!parsedUrl.protocol) {
protocol = 'https';
}
var url = nodeURL.format({
let url = nodeURL.format({
protocol: protocol,
slahes: true,
pathname: parsedUrl.href
@ -246,7 +278,7 @@ function processProtocolAction(argv) {
return;
}
let protocolUri = getCmdLineArg(argv, 'symphony://');
let protocolUri = getCmdLineArg(argv, 'symphony://', false);
if (protocolUri) {
@ -261,6 +293,10 @@ function processProtocolAction(argv) {
}
}
/**
* Handles a protocol action based on the current state of the app
* @param uri
*/
function handleProtocolAction(uri) {
if (!isAppAlreadyOpen) {
// app is opened by the protocol url, cache the protocol url to be used later
@ -269,4 +305,4 @@ function handleProtocolAction(uri) {
// app is already open, so, just trigger the protocol action method
protocolHandler.processProtocolAction(uri);
}
}
}

View File

@ -9,7 +9,7 @@ const electron = require('electron');
const windowMgr = require('./windowMgr.js');
const log = require('./log.js');
const logLevels = require('./enums/logLevels');
const activityDetection = require('./activityDetection/activityDetection');
const activityDetection = require('./activityDetection');
const badgeCount = require('./badgeCount.js');
const protocolHandler = require('./protocolHandler');
const configureNotification = require('./notify/settings/configure-notification-position');
@ -30,7 +30,7 @@ function isValidWindow(event) {
if (!checkValidWindow) {
return true;
}
var result = false;
let result = false;
if (event && event.sender) {
// validate that event sender is from window we created
const browserWin = electron.BrowserWindow.fromWebContents(event.sender);
@ -107,7 +107,7 @@ electron.ipcMain.on(apiName, (event, arg) => {
// expose these methods primarily for testing...
module.exports = {
shouldCheckValidWindow: function (shouldCheck) {
shouldCheckValidWindow: function(shouldCheck) {
checkValidWindow = shouldCheck;
}
};
};

View File

@ -1,14 +1,17 @@
'use strict';
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js')
const logLevels = require('./enums/logLevels.js');
// once a minute
setInterval(gatherMemory, 1000 * 60);
/**
* Gathers system memory and logs it to the remote system
*/
function gatherMemory() {
var memory = process.getProcessMemoryInfo();
var details =
let memory = process.getProcessMemoryInfo();
let details =
'workingSetSize: ' + memory.workingSetSize +
' peakWorkingSetSize: ' + memory.peakWorkingSetSize +
' privatesBytes: ' + memory.privatesBytes +

View File

@ -1,18 +1,17 @@
'use strict';
const electron = require('electron');
const { getConfigField, updateConfigField } = require('../config.js');
const { updateConfigField, getMultipleConfigField } = require('../config.js');
const AutoLaunch = require('auto-launch');
const isMac = require('../utils/misc.js').isMac;
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const eventEmitter = require('../eventEmitter');
const aboutApp = require('../aboutApp');
var minimizeOnClose = false;
var launchOnStartup = false;
var isAlwaysOnTop = false;
setCheckboxValues();
let minimizeOnClose = false;
let launchOnStartup = false;
let isAlwaysOnTop = false;
let symphonyAutoLauncher;
@ -31,10 +30,9 @@ if (isMac) {
});
}
const template = [
{
label: 'Edit',
submenu: [
const template = [{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
@ -44,265 +42,272 @@ const template = [
{ role: 'pasteandmatchstyle' },
{ role: 'delete' },
{ role: 'selectall' }
]
]
},
{
label: 'View',
submenu: [{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.reload();
}
}
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.reload();
}
}
},
{
label: 'Toggle Developer Tools',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
}
},
{
type: 'separator'
},
{
role: 'resetzoom'
},
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
label: 'Toggle Developer Tools',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
]
}
},
{
role: 'window',
submenu: [
{
role: 'minimize'
},
{
role: 'close'
}
]
label: 'Open Crashes Directory',
click() {
const crashesDirectory = electron.crashReporter.getCrashesDirectory() + '/completed';
electron.shell.showItemInFolder(crashesDirectory);
}
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click () { electron.shell.openExternal('https://www.symphony.com') }
}
]
type: 'separator'
},
{
role: 'resetzoom'
},
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
}
]
},
{
role: 'window',
submenu: [{
role: 'minimize'
},
{
role: 'close'
}
]
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click() { electron.shell.openExternal('https://www.symphony.com'); }
}]
}
];
function getTemplate(app) {
if (isMac && template[0].label !== app.getName()) {
template.unshift({
label: app.getName(),
submenu: [
{
role: 'about'
},
{
type: 'separator'
},
{
role: 'services',
submenu: []
},
{
type: 'separator'
},
{
role: 'hide'
},
{
role: 'hideothers'
},
{
role: 'unhide'
},
{
type: 'separator'
},
{
role: 'quit'
}
submenu: [{
role: 'about'
},
{
type: 'separator'
},
{
role: 'services',
submenu: []
},
{
type: 'separator'
},
{
role: 'hide'
},
{
role: 'hideothers'
},
{
role: 'unhide'
},
{
type: 'separator'
},
{
role: 'quit'
}
]
});
// Edit menu.
template[1].submenu.push(
{
type: 'separator'
// Edit menu.
template[1].submenu.push({
type: 'separator'
}, {
label: 'Speech',
submenu: [{
role: 'startspeaking'
},
{
label: 'Speech',
submenu: [
{
role: 'startspeaking'
},
{
role: 'stopspeaking'
}
]
role: 'stopspeaking'
}
)
// Window menu.
template[3].submenu = [
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
},
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Zoom',
role: 'zoom'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
}
]
]
});
// Window menu.
template[3].submenu = [{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
},
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Zoom',
role: 'zoom'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
}
];
}
var index = 2;
if (isMac && template[0].label !== app.getName()){
let index = 2;
if (isMac && template[0].label !== app.getName()) {
index = 3;
}
// Window menu -> launchOnStartup.
template[index].submenu.push(
{
label: 'Auto Launch On Startup',
type: 'checkbox',
checked: launchOnStartup,
click: function (item) {
if (item.checked){
symphonyAutoLauncher.enable()
.catch(function (err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
} else {
symphonyAutoLauncher.disable()
.catch(function (err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
}
launchOnStartup = item.checked;
updateConfigField('launchOnStartup', launchOnStartup);
template[index].submenu.push({
label: 'Auto Launch On Startup',
type: 'checkbox',
checked: launchOnStartup,
click: function(item) {
if (item.checked) {
symphonyAutoLauncher.enable()
.catch(function(err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
} else {
symphonyAutoLauncher.disable()
.catch(function(err) {
let title = 'Error setting AutoLaunch configuration';
log.send(logLevels.ERROR, 'MenuTemplate: ' + title + ': auto launch error ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
}
launchOnStartup = item.checked;
updateConfigField('launchOnStartup', launchOnStartup);
}
)
});
// Window menu -> alwaysOnTop.
template[index].submenu.push(
{
label: 'Always on top',
type: 'checkbox',
checked: isAlwaysOnTop,
click: (item) => {
isAlwaysOnTop = item.checked;
eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
updateConfigField('alwaysOnTop', isAlwaysOnTop);
}
template[index].submenu.push({
label: 'Always on top',
type: 'checkbox',
checked: isAlwaysOnTop,
click: (item) => {
isAlwaysOnTop = item.checked;
eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
updateConfigField('alwaysOnTop', isAlwaysOnTop);
}
)
});
// Window menu -> minimizeOnClose.
// Window menu -> minimizeOnClose.
// ToDo: Add behavior on Close.
template[index].submenu.push(
{
label: 'Minimize on Close',
type: 'checkbox',
checked: minimizeOnClose,
click: function (item) {
minimizeOnClose = item.checked;
updateConfigField('minimizeOnClose', minimizeOnClose);
}
template[index].submenu.push({
label: 'Minimize on Close',
type: 'checkbox',
checked: minimizeOnClose,
click: function(item) {
minimizeOnClose = item.checked;
updateConfigField('minimizeOnClose', minimizeOnClose);
}
)
});
if (!isMac){
template[index].submenu.push(
{
label: 'Quit Symphony',
click: function () {
app.quit();
}
if (!isMac) {
template[index].submenu.push({
label: 'Quit Symphony',
click: function() {
app.quit();
}
)
});
// This adds About Symphony under help menu for windows
template[3].submenu.push({
label: 'About Symphony',
click(focusedWindow) {
let windowName = focusedWindow ? focusedWindow.name : '';
aboutApp.openAboutWindow(windowName);
}
});
}
return template;
}
function setCheckboxValues(){
getConfigField('minimizeOnClose').then(function(mClose) {
minimizeOnClose = mClose;
}).catch(function (err){
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field minimizeOnClose, error: ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
/**
* Sets the checkbox values for different menu items
* based on configuration
*/
function setCheckboxValues() {
return new Promise((resolve) => {
/**
* Method that reads multiple config fields
*/
getMultipleConfigField(['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'notificationSettings'])
.then(function (configData) {
for (let key in configData) {
if (configData.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins
switch (key) {
case 'minimizeOnClose':
minimizeOnClose = configData[key];
break;
case 'launchOnStartup':
launchOnStartup = configData[key];
break;
case 'alwaysOnTop':
isAlwaysOnTop = configData[key];
eventEmitter.emit('isAlwaysOnTop', configData[key]);
break;
case 'notificationSettings':
eventEmitter.emit('notificationSettings', configData[key]);
break;
default:
break;
}
}
}
return resolve();
})
.catch((err) => {
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error reading configuration fields, error: ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
return resolve();
});
});
getConfigField('launchOnStartup').then(function(lStartup) {
launchOnStartup = lStartup;
}).catch(function (err){
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field launchOnStartup, error: ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
getConfigField('alwaysOnTop').then(function(mAlwaysOnTop) {
isAlwaysOnTop = mAlwaysOnTop;
eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
}).catch(function (err){
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field alwaysOnTop, error: ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
getConfigField('notificationSettings').then(function(notfObject) {
eventEmitter.emit('notificationSettings', notfObject);
}).catch(function (err){
let title = 'Error loading configuration';
log.send(logLevels.ERROR, 'MenuTemplate: error getting config field notificationSettings, error: ' + err);
electron.dialog.showErrorBox(title, title + ': ' + err);
});
}
function getMinimizeOnClose(){
function getMinimizeOnClose() {
return minimizeOnClose;
}
module.exports = {
getTemplate : getTemplate,
getMinimizeOnClose : getMinimizeOnClose
getTemplate: getTemplate,
getMinimizeOnClose: getMinimizeOnClose,
setCheckboxValues: setCheckboxValues
};

View File

@ -3,13 +3,21 @@
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
// One animation at a time
/**
* Manages one animation at a time
* @param options
* @constructor
*/
const AnimationQueue = function(options) {
this.options = options;
this.queue = [];
this.running = false;
}
};
/**
* Pushes each animation to a queue
* @param object
*/
AnimationQueue.prototype.push = function(object) {
if (this.running) {
this.queue.push(object);
@ -17,8 +25,12 @@ AnimationQueue.prototype.push = function(object) {
this.running = true;
setTimeout(this.animate.bind(this, object), 0);
}
}
};
/**
* Animates an animation that is part of the queue
* @param object
*/
AnimationQueue.prototype.animate = function(object) {
object.func.apply(null, object.args)
.then(function() {
@ -37,10 +49,13 @@ AnimationQueue.prototype.animate = function(object) {
' with stack trace:' + err.stack);
/* eslint-enable no-console */
})
}
};
/**
* Clears the queue
*/
AnimationQueue.prototype.clear = function() {
this.queue = [];
}
};
module.exports = AnimationQueue;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,4 +1,4 @@
'use strict'
'use strict';
//
// BrowserWindow preload script use to create notifications window for
@ -9,37 +9,54 @@
const electron = require('electron');
const ipc = electron.ipcRenderer;
const whiteColorRegExp = new RegExp(/^(?:white|#fff(?:fff)?|rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*1\s*)?\))$/i);
/**
* Sets style for a notification
* @param config
*/
function setStyle(config) {
// Style it
let notiDoc = window.document;
let container = notiDoc.getElementById('container');
let header = notiDoc.getElementById('header');
let image = notiDoc.getElementById('image');
let logo = notiDoc.getElementById('symphony-logo');
let title = notiDoc.getElementById('title');
let pod = notiDoc.getElementById('pod');
let message = notiDoc.getElementById('message');
let close = notiDoc.getElementById('close');
// Default style
setStyleOnDomElement(config.defaultStyleContainer, container)
setStyleOnDomElement(config.defaultStyleContainer, container);
let style = {
height: config.height,
width: config.width,
borderRadius: config.borderRadius + 'px'
}
setStyleOnDomElement(style, container)
};
setStyleOnDomElement(style, container);
setStyleOnDomElement(config.defaultStyleHeader, header);
setStyleOnDomElement(config.defaultStyleImage, image);
setStyleOnDomElement(config.defaultStyleLogo, logo);
setStyleOnDomElement(config.defaultStyleTitle, title);
setStyleOnDomElement(config.defaultStylePod, pod);
setStyleOnDomElement(config.defaultStyleText, message);
setStyleOnDomElement(config.defaultStyleClose, close);
}
/**
* Sets contents for a notification
* @param event
* @param notificationObj
*/
function setContents(event, notificationObj) {
// sound
if (notificationObj.sound) {
@ -49,7 +66,7 @@ function setContents(event, notificationObj) {
// Won't check remote files e.g. http://
if (notificationObj.sound.match(/^file:/) !== null
|| notificationObj.sound.match(/^\//) !== null) {
let audio = new window.Audio(notificationObj.sound)
let audio = new window.Audio(notificationObj.sound);
audio.play()
}
} catch (e) {
@ -66,6 +83,20 @@ function setContents(event, notificationObj) {
if (notificationObj.color) {
container.style.backgroundColor = notificationObj.color;
let logo = notiDoc.getElementById('symphony-logo');
if (notificationObj.color.match(whiteColorRegExp)) {
logo.src = './assets/symphony-logo-black.png';
} else {
let title = notiDoc.getElementById('title');
let pod = notiDoc.getElementById('pod');
let message = notiDoc.getElementById('message');
message.style.color = '#ffffff';
title.style.color = '#ffffff';
pod.style.color = notificationObj.color;
logo.src = './assets/symphony-logo-white.png';
}
}
if (notificationObj.flash) {
@ -102,15 +133,20 @@ function setContents(event, notificationObj) {
// note: use onclick because we only want one handler, for case
// when content gets overwritten by notf with same tag
closeButton.onclick = function(clickEvent) {
clickEvent.stopPropagation()
clickEvent.stopPropagation();
ipc.send('electron-notify-close', winId, notificationObj)
}
};
container.onclick = function() {
ipc.send('electron-notify-click', winId, notificationObj);
}
}
/**
* Sets style on a notification for a DOM element
* @param styleObj
* @param domElement
*/
function setStyleOnDomElement(styleObj, domElement) {
try {
let styleAttr = Object.keys(styleObj);
@ -124,22 +160,30 @@ function setStyleOnDomElement(styleObj, domElement) {
}
}
/**
* Loads the config
* @param event
* @param conf
*/
function loadConfig(event, conf) {
setStyle(conf || {})
}
/**
* Resets the notification window
*/
function reset() {
let notiDoc = window.document
let container = notiDoc.getElementById('container')
let closeButton = notiDoc.getElementById('close')
let notiDoc = window.document;
let container = notiDoc.getElementById('container');
let closeButton = notiDoc.getElementById('close');
// Remove event listener
let newContainer = container.cloneNode(true)
container.parentNode.replaceChild(newContainer, container)
let newCloseButton = closeButton.cloneNode(true)
let newContainer = container.cloneNode(true);
container.parentNode.replaceChild(newContainer, container);
let newCloseButton = closeButton.cloneNode(true);
closeButton.parentNode.replaceChild(newCloseButton, closeButton)
}
ipc.on('electron-notify-set-contents', setContents)
ipc.on('electron-notify-load-config', loadConfig)
ipc.on('electron-notify-reset', reset)
ipc.on('electron-notify-set-contents', setContents);
ipc.on('electron-notify-load-config', loadConfig);
ipc.on('electron-notify-reset', reset);

View File

@ -2,15 +2,21 @@
<head></head>
<body style='margin:0; overflow: hidden; -webkit-user-select: none;'>
<div id="container">
<div id="header">
<img src="" id="image" />
<div>
<img src="" id="symphony-logo">
</div>
<div id="header">
<span id="title"></span>
<span id="pod"></span>
<span id="message"></span>
</div>
<p id="message"></p>
<div id="picture">
<img src="" id="image" style="border-radius: 4px" />
</div>
<div id="close">
<svg fill="#000000" height="16" viewBox="0 0 24 24" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
<path d="M0 0h24v24H0z" fill="none"></path>
</svg>
</div>
</div>

View File

@ -1,4 +1,4 @@
'use strict'
'use strict';
//
// code here adapted from https://www.npmjs.com/package/electron-notify
// made following changes:
@ -55,9 +55,9 @@ let config = {
// corner to put notifications
// upper-right, upper-left, lower-right, lower-left
startCorner: 'upper-right',
width: 300,
height: 80,
borderRadius: 2,
width: 380,
height: 70,
borderRadius: 5,
displayTime: 5000,
animationSteps: 5,
animationStepMs: 5,
@ -67,63 +67,66 @@ let config = {
defaultStyleContainer: {
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#f0f0f0',
overflow: 'hidden',
padding: 10,
position: 'relative',
lineHeight: '15px',
boxSizing: 'border-box'
},
defaultStyleHeader: {
flex: '0 0 auto',
display: 'flex',
flexDirection: 'row'
width: 245,
minWidth: 230,
margin: "12px 10px"
},
defaultStyleImage: {
flex: '0 0 auto',
overflow: 'hidden',
height: 30,
width: 30,
marginLeft: 0,
marginRight: 8
height: 43,
borderRadius: 4,
marginTop: 12,
width: 43
},
defaultStyleClose: {
position: 'absolute',
top: 8,
right: 8,
width: 16,
height: 16,
margin: "10px 8px 0 8px",
opacity: 0.54,
fontSize: 12,
color: '#CCC'
},
defaultStyleTitle: {
fontFamily: 'Arial',
fontFamily: 'sans-serif',
fontSize: 14,
fontWeight: 700,
opacity: 0.87,
marginRight: 10,
alignSelf: 'center',
color: '#4a4a4a',
overflow: 'hidden',
display: '-webkit-box',
webkitLineClamp: 2,
webkitLineClamp: 1,
webkitBoxOrient: 'vertical',
},
defaultStylePod: {
fontFamily: 'sans-serif',
fontSize: 11,
color: '#adadad',
overflow: 'hidden',
filter: 'brightness(70%)',
display: '-webkit-box',
webkitLineClamp: 1,
webkitBoxOrient: 'vertical',
},
defaultStyleText: {
flex: '0 0 auto',
fontFamily: 'Calibri',
fontSize: 14,
fontWeight: 400,
opacity: 0.87,
margin: 0,
marginTop: 4,
fontFamily: 'sans-serif',
fontSize: 12,
color: '#4a4a4a',
marginTop: 12,
overflow: 'hidden',
display: '-webkit-box',
webkitLineClamp: 2,
webkitLineClamp: 1,
webkitBoxOrient: 'vertical',
cursor: 'default'
},
defaultStyleLogo: {
margin: "12px 0 0 -12px",
opacity: 0.6,
},
defaultWindow: {
alwaysOnTop: true,
skipTaskbar: true,
@ -138,13 +141,7 @@ let config = {
nodeIntegration: isNodeEnv
}
}
}
// function setConfig(customConfig) {
// Object.assign(customConfig, config);
//
// calcDimensions();
// }
};
if (app.isReady()) {
setup();
@ -152,7 +149,10 @@ if (app.isReady()) {
app.on('ready', setup);
}
// Method to update notification config
/**
* Method to update notification config
* @param customConfig
*/
function updateConfig(customConfig) {
// Fetching user preferred notification position from config
if (customConfig.position) {
@ -167,16 +167,23 @@ function updateConfig(customConfig) {
}
}
/**
* Method to setup the notification configuration
*/
function setup() {
setupConfig();
// if display added/removed/changed then re-run setup and remove all existing
// notifications. ToDo: should reposition notifications rather than closing.
// notifications.
electron.screen.on('display-added', setupConfig);
electron.screen.on('display-removed', setupConfig);
electron.screen.on('display-metrics-changed', setupConfig);
}
/**
* Method to get the notification template path
* @returns {string|*}
*/
function getTemplatePath() {
let templatePath = path.join(__dirname, 'electron-notify.html');
try {
@ -188,12 +195,15 @@ function getTemplatePath() {
return config.templatePath;
}
/**
* Calculates the dimensions of the screen
*/
function calcDimensions() {
const vertSpaceBetweenNotf = 8;
// Calc totalHeight & totalWidth
config.totalHeight = config.height + vertSpaceBetweenNotf;
config.totalWidth = config.width
config.totalWidth = config.width;
let firstPosX, firstPosY;
switch (config.startCorner) {
@ -220,15 +230,17 @@ function calcDimensions() {
config.firstPos = {
x: firstPosX,
y: firstPosY
}
};
// Set nextInsertPos
nextInsertPos.x = config.firstPos.x
nextInsertPos.x = config.firstPos.x;
nextInsertPos.y = config.firstPos.y
}
/**
* Setup the notification config
*/
function setupConfig() {
closeAll();
// This feature only applies to windows
if (!isMac) {
@ -273,6 +285,11 @@ function setupConfig() {
config.maxVisibleNotifications = config.maxVisibleNotifications > 5 ? 5 : config.maxVisibleNotifications;
}
/**
* Notifies the user
* @param notification
* @returns {*}
*/
function notify(notification) {
// Is it an object and only one argument?
if (arguments.length === 1 && typeof notification === 'object') {
@ -283,17 +300,25 @@ function notify(notification) {
animationQueue.push({
func: showNotification,
args: [ notf ]
})
});
return notf.id
}
log.send(logLevels.ERROR, 'electron-notify: ERROR notify() only accepts a single object with notification parameters.');
return null;
}
/**
* Increment the notification
*/
function incrementId() {
latestID++;
}
/**
* Shows the notification to the user
* @param notificationObj
* @returns {Promise}
*/
function showNotification(notificationObj) {
return new Promise(function(resolve) {
@ -349,7 +374,7 @@ function showNotification(notificationObj) {
});
delete notificationWindow.electronNotifyOnCloseFunc;
}
setNotificationContents(notificationWindow, notificationObj)
setNotificationContents(notificationWindow, notificationObj);
resolve();
return;
}
@ -361,8 +386,8 @@ function showNotification(notificationObj) {
// Get inactiveWindow or create new:
getWindow().then(function(notificationWindow) {
// Move window to position
calcInsertPos()
setWindowPosition(notificationWindow, nextInsertPos.x, nextInsertPos.y)
calcInsertPos();
setWindowPosition(notificationWindow, nextInsertPos.x, nextInsertPos.y);
let updatedNotfWindow = setNotificationContents(notificationWindow, notificationObj);
@ -378,6 +403,12 @@ function showNotification(notificationObj) {
})
}
/**
* Sets the HTML notification contents along with other options
* @param notfWindow
* @param notfObj
* @returns {*}
*/
function setNotificationContents(notfWindow, notfObj) {
// Display time per notification basis.
@ -387,7 +418,7 @@ function setNotificationContents(notfWindow, notfObj) {
clearTimeout(notfWindow.displayTimer);
}
var updatedNotificationWindow = notfWindow;
const updatedNotificationWindow = notfWindow;
updatedNotificationWindow.notfyObj = notfObj;
@ -437,7 +468,13 @@ function setNotificationContents(notfWindow, notfObj) {
return updatedNotificationWindow;
}
// Close notification function
/**
* Closes the notification
* @param notificationWindow
* @param notificationObj
* @param getTimeoutId
* @returns {Function}
*/
function buildCloseNotification(notificationWindow, notificationObj, getTimeoutId) {
return function(event) {
if (closedNotifications[notificationObj.id]) {
@ -459,7 +496,7 @@ function buildCloseNotification(notificationWindow, notificationObj, getTimeoutI
}
// reset content
notificationWindow.webContents.send('electron-notify-reset')
notificationWindow.webContents.send('electron-notify-reset');
if (getTimeoutId && typeof getTimeoutId === 'function') {
let timeoutId = getTimeoutId();
clearTimeout(timeoutId);
@ -480,8 +517,13 @@ function buildCloseNotification(notificationWindow, notificationObj, getTimeoutI
}
}
// Always add to animationQueue to prevent erros (e.g. notification
// got closed while it was moving will produce an error)
/**
* Adds an active notification the close notification queue
* Always add to animationQueue to prevent erros (e.g. notification
* got closed while it was moving will produce an error)
* @param closeFunc
* @returns {Function}
*/
function buildCloseNotificationSafely(closeFunc) {
return function(reason) {
animationQueue.push({
@ -509,10 +551,10 @@ ipc.on('electron-notify-click', function (event, winId, notificationObj) {
}
});
/*
* Checks for queued notifications and add them
* to AnimationQueue if possible
*/
/**
* Checks for queued notifications and add them
* to AnimationQueue if possible
*/
function checkForQueuedNotifications() {
if (notificationQueue.length > 0 &&
activeNotifications.length < config.maxVisibleNotifications) {
@ -524,25 +566,25 @@ function checkForQueuedNotifications() {
}
}
/*
* Moves the notifications one position down,
* starting with notification at startPos
*
* @param {int} startPos
*/
/**
* Moves the notifications one position down,
* starting with notification at startPos
* @param startPos
* @returns {Promise}
*/
function moveOneDown(startPos) {
return new Promise(function(resolve) {
if (startPos >= activeNotifications || startPos === -1) {
resolve()
resolve();
return
}
// Build array with index of affected notifications
let notificationPosArray = []
let notificationPosArray = [];
for (let i = startPos; i < activeNotifications.length; i++) {
notificationPosArray.push(i)
}
// Start to animate all notifications at once or in parallel
let asyncFunc = asyncMap // Best performance
let asyncFunc = asyncMap; // Best performance
if (config.animateInParallel === false) {
asyncFunc = asyncMapSeries // Sluggish
}
@ -552,6 +594,11 @@ function moveOneDown(startPos) {
})
}
/**
* Moves the notification animation
* @param i
* @param done
*/
function moveNotificationAnimation(i, done) {
// Get notification to move
let notificationWindow = activeNotifications[i];
@ -571,32 +618,38 @@ function moveNotificationAnimation(i, done) {
}
// Get startPos, calc step size and start animationInterval
let startY = notificationWindow.getPosition()[1]
let step = (newY - startY) / config.animationSteps
let curStep = 1
let startY = notificationWindow.getPosition()[1];
let step = (newY - startY) / config.animationSteps;
let curStep = 1;
let animationInterval = setInterval(function() {
// Abort condition
if (curStep === config.animationSteps) {
setWindowPosition(notificationWindow, config.firstPos.x, newY);
clearInterval(animationInterval)
clearInterval(animationInterval);
done(null, 'done');
return;
}
// Move one step down
setWindowPosition(notificationWindow, config.firstPos.x, startY + curStep * step)
setWindowPosition(notificationWindow, config.firstPos.x, startY + curStep * step);
curStep++
}, config.animationStepMs)
}
/**
* Sets the window's position
* @param browserWin
* @param posX
* @param posY
*/
function setWindowPosition(browserWin, posX, posY) {
if (!browserWin.isDestroyed()) {
browserWin.setPosition(parseInt(posX, 10), parseInt(posY, 10))
}
}
/*
* Find next possible insert position (on top)
*/
/**
* Find next possible insert position (on top)
*/
function calcInsertPos() {
if (activeNotifications.length < config.maxVisibleNotifications) {
switch(config.startCorner) {
@ -614,64 +667,43 @@ function calcInsertPos() {
}
}
/*
* Get a window to display a notification. Use inactiveWindows or
* create a new window
* @return {Window}
*/
/**
* Get a window to display a notification. Use inactiveWindows or
* create a new window
* @returns {Promise}
*/
function getWindow() {
return new Promise(function(resolve) {
let notificationWindow
let notificationWindow;
// Are there still inactiveWindows?
if (inactiveWindows.length > 0) {
notificationWindow = inactiveWindows.pop()
notificationWindow = inactiveWindows.pop();
resolve(notificationWindow)
} else {
// Or create a new window
let windowProperties = config.defaultWindow
windowProperties.width = config.width
windowProperties.height = config.height
notificationWindow = new BrowserWindow(windowProperties)
notificationWindow.setVisibleOnAllWorkspaces(true)
notificationWindow.loadURL(getTemplatePath())
let windowProperties = config.defaultWindow;
windowProperties.width = config.width;
windowProperties.height = config.height;
notificationWindow = new BrowserWindow(windowProperties);
notificationWindow.setVisibleOnAllWorkspaces(true);
notificationWindow.loadURL(getTemplatePath());
notificationWindow.webContents.on('did-finish-load', function() {
// Done
notificationWindow.webContents.send('electron-notify-load-config', config)
notificationWindow.webContents.send('electron-notify-load-config', config);
resolve(notificationWindow)
})
}
})
}
function closeAll() {
// Clear out animation Queue and close windows
animationQueue.clear();
activeNotifications.forEach(function(window) {
if (window.displayTimer) {
clearTimeout(window.displayTimer);
}
if (window.electronNotifyOnCloseFunc) {
// ToDo: fix this: shouldn't delete method on arg
/* eslint-disable */
delete window.electronNotifyOnCloseFunc;
/* eslint-enable */
}
window.close();
});
cleanUpInactiveWindow();
// Reset certain vars
nextInsertPos = {};
activeNotifications = [];
}
/**
/* once a minute, remove inactive windows to free up memory used.
* Once a minute, remove inactive windows to free up memory used.
*/
setInterval(cleanUpInactiveWindow, 60000);
/**
* Cleans up inactive windows
*/
function cleanUpInactiveWindow() {
inactiveWindows.forEach(function(window) {
window.close();
@ -679,6 +711,6 @@ function cleanUpInactiveWindow() {
inactiveWindows = [];
}
module.exports.notify = notify
module.exports.updateConfig = updateConfig
module.exports.reset = setupConfig
module.exports.notify = notify;
module.exports.updateConfig = updateConfig;
module.exports.reset = setupConfig;

View File

@ -51,6 +51,10 @@ class Notify {
this._data = options.data || null;
/**
* Handles on show event
* @param arg
*/
function onShow(arg) {
if (arg.id === this._id) {
log.send(logLevels.INFO, 'showing notification, id=' + this._id);
@ -61,6 +65,10 @@ class Notify {
}
}
/**
* Handles on click event
* @param arg
*/
function onClick(arg) {
if (arg.id === this._id) {
log.send(logLevels.INFO, 'clicking notification, id=' + this._id);
@ -70,6 +78,10 @@ class Notify {
}
}
/**
* Handles on close event
* @param arg
*/
function onClose(arg) {
if (arg.id === this._id || arg.event === 'close-all') {
log.send(logLevels.INFO, 'closing notification, id=' + this._id);
@ -80,6 +92,10 @@ class Notify {
}
}
/**
* Handles on error event
* @param arg
*/
function onError(arg) {
if (arg.id === this._id) {
// don't raise error event if handler doesn't exist, node
@ -95,7 +111,7 @@ class Notify {
}
/**
* close notification
* Closes notification
*/
close() {
if (typeof this._closeNotification === 'function') {
@ -105,7 +121,7 @@ class Notify {
}
/**
* always allow showing notifications.
* Always allow showing notifications.
* @return {string} 'granted'
*/
static get permission() {
@ -113,14 +129,14 @@ class Notify {
}
/**
* returns data object passed in via constructor options
* Returns data object passed in via constructor options
*/
get data() {
return this._data;
}
/**
* add event listeners for 'click', 'close', 'show', 'error' events
* Adds event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to listen for
* @param {func} cb callback invoked when event occurs
@ -132,7 +148,7 @@ class Notify {
}
/**
* remove event listeners for 'click', 'close', 'show', 'error' events
* Removes event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to stop listening for.
* @param {func} cb callback associated with original addEventListener
@ -144,7 +160,7 @@ class Notify {
}
/**
* removes all event listeners
* Removes all event listeners
*/
removeAllEvents() {
this.destroy();
@ -168,10 +184,10 @@ class Notify {
*/
function Queue(emitter) {
/**
* Cache emitter on.
* @api private
*/
var cache = emitter.on;
* Cache emitter on.
* @api private
*/
const cache = emitter.on;
let modifiedEmitter = emitter;
/**
* Emit event and store it if no
@ -180,7 +196,7 @@ function Queue(emitter) {
*
* .queue('message', 'hi');
*
* @param {String} event
* @param {String} topic
*/
modifiedEmitter.queue = function(topic) {
this._queue = this._queue || {};
@ -191,18 +207,18 @@ function Queue(emitter) {
(this._queue[topic] = this._queue[topic] || [])
.push([].slice.call(arguments, 1));
}
}
};
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @return {Event}
*/
modifiedEmitter.on = modifiedEmitter.addEventListener = function(topic, fn) {
this._queue = this._queue || {};
var topics = this._queue[topic];
const topics = this._queue[topic];
cache.apply(this, arguments);
if (!this._callbacks) {
@ -211,7 +227,9 @@ function Queue(emitter) {
this._callbacks[topic] = true;
if (topics) {
for(var i = 0, l = topics.length; i < l; i++) {
let i = 0;
const l = topics.length;
for(; i < l; i++) {
fn.apply(this, topics[i]);
}
delete this._queue[topic];

View File

@ -9,7 +9,9 @@ let selectedDisplay;
renderSettings();
// Method that renders the data from user config
/**
* Method that renders the data from user config
*/
function renderSettings() {
document.addEventListener('DOMContentLoaded', function () {
@ -33,6 +35,9 @@ function renderSettings() {
}
/**
* Updates the configuration and closes the alert
*/
function updateAndClose() {
ipc.send('update-config', {position: selectedPosition, display: selectedDisplay});
ipc.send('close-alert');

View File

@ -15,7 +15,7 @@
<label class="label">Monitor</label>
<div id="screens" class="main">
<label>Notification shown on Monitor: </label>
<select class="selector" id="screen-selector">
<select class="selector" id="screen-selector" title="position">
</select>
</div>
<label class="label">Position</label>

View File

@ -41,15 +41,28 @@ app.on('ready', () => {
electron.screen.on('display-removed', updateScreens);
});
/**
* Update all the screens
*/
function updateScreens() {
screens = electron.screen.getAllDisplays();
// Notifying renderer when a display is added/removed
if (configurationWindow && screens && screens.length >= 0) {
configurationWindow.webContents.send('screens', screens);
if (configurationWindow) {
// Event that updates the DOM elements
// notification position checkbox and monitor selection drop-down
configurationWindow.webContents.send('notificationSettings', {position: position, display: display});
if (screens && screens.length >= 0) {
configurationWindow.webContents.send('screens', screens);
}
}
}
/**
* Gets the template path
* @returns {string}
*/
function getTemplatePath() {
let templatePath = path.join(__dirname, 'configure-notification-position.html');
try {
@ -60,14 +73,35 @@ function getTemplatePath() {
return 'file://' + templatePath;
}
/**
* Opens the configuration window for a specific window
* @param windowName
*/
function openConfigurationWindow(windowName) {
let allWindows = BrowserWindow.getAllWindows();
allWindows = allWindows.find((window) => { return window.winName === windowName });
const allWindows = BrowserWindow.getAllWindows();
const selectedParentWindow = allWindows.find((window) => { return window.winName === windowName });
// if we couldn't find any window matching the window name
// it will render as a new window
if (allWindows) {
windowConfig.parent = allWindows;
if (selectedParentWindow) {
windowConfig.parent = selectedParentWindow;
/**
* This is a temporary work around until there
* is a fix for the modal window in windows from the electron
* issue - https://github.com/electron/electron/issues/10721
*/
const { x, y, width, height } = selectedParentWindow.getBounds();
const windowWidth = Math.round(width * 0.5);
const windowHeight = Math.round(height * 0.5);
// Calculating the center of the parent window
// to place the configuration window
const centerX = x + width / 2.0;
const centerY = y + height / 2.0;
windowConfig.x = Math.round(centerX - (windowWidth / 2.0));
windowConfig.y = Math.round(centerY - (windowHeight / 2.0));
}
configurationWindow = new BrowserWindow(windowConfig);
@ -94,6 +128,9 @@ function openConfigurationWindow(windowName) {
});
}
/**
* Destroys a window
*/
function destroyWindow() {
configurationWindow = null;
}

View File

@ -11,7 +11,7 @@
// also to bring pieces of node.js:
// https://github.com/electron/electron/issues/2984
//
const { ipcRenderer, remote } = require('electron');
const { ipcRenderer, remote, crashReporter } = require('electron');
const throttle = require('../utils/throttle.js');
const apiEnums = require('../enums/api.js');
@ -19,16 +19,23 @@ const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const getMediaSources = require('../desktopCapturer/getSources');
require('../downloadManager/downloadManager');
require('../downloadManager');
// bug in electron preventing us from using spellchecker in pop outs
// https://github.com/electron/electron/issues/4025
// so loading the spellchecker in try catch so that we don't
// block other method from loading
document.addEventListener('DOMContentLoaded', () => {
loadSpellChecker();
});
/**
* Loads up the spell checker module
*/
function loadSpellChecker() {
try {
/* eslint-disable global-require */
const SpellCheckerHelper = require('../spellChecker/spellChecker').SpellCheckHelper;
const SpellCheckerHelper = require('../spellChecker').SpellCheckHelper;
/* eslint-enable global-require */
// Method to initialize spell checker
const spellChecker = new SpellCheckerHelper();
@ -38,9 +45,7 @@ document.addEventListener('DOMContentLoaded', () => {
console.error('unable to load the spell checker module, hence, skipping the spell check feature ' + err);
/* eslint-enable no-console */
}
});
const nodeURL = require('url');
}
// hold ref so doesn't get GC'ed
const local = {
@ -66,25 +71,6 @@ function createAPI() {
return;
}
// bug in electron is preventing using event 'will-navigate' from working
// in sandboxed environment. https://github.com/electron/electron/issues/8841
// so in the mean time using this code below to block clicking on A tags.
// A tags are allowed if they include href='_blank', this cause 'new-window'
// event to be received which is handled properly in windowMgr.js
window.addEventListener('beforeunload', function(event) {
var newUrl = document.activeElement && document.activeElement.href;
if (newUrl) {
var currHostName = window.location.hostname;
var parsedNewUrl = nodeURL.parse(newUrl);
var parsedNewUrlHostName = parsedNewUrl && parsedNewUrl.hostname;
if (currHostName !== parsedNewUrlHostName) {
/* eslint-disable no-param-reassign */
event.returnValue = 'false';
/* eslint-enable no-param-reassign */
}
}
});
// note: window.open from main window (if in the same domain) will get
// api access. window.open in another domain will be opened in the default
// browser (see: handler for event 'new-window' in windowMgr.js)
@ -95,14 +81,14 @@ function createAPI() {
window.ssf = {
getVersionInfo: function() {
return new Promise(function(resolve) {
var appName = remote.app.getName();
var appVer = remote.app.getVersion();
let appName = remote.app.getName();
let appVer = remote.app.getVersion();
const verInfo = {
containerIdentifier: appName,
containerVer: appVer,
apiVer: '1.0.0'
}
};
resolve(verInfo);
});
},
@ -126,9 +112,22 @@ function createAPI() {
/**
* provides api to allow user to capture portion of screen, see api
* details in screenSnipper/ScreenSnippet.js
* details in screenSnipper/index.js
*/
ScreenSnippet: remote.require('./screenSnippet/ScreenSnippet.js').ScreenSnippet,
ScreenSnippet: remote.require('./screenSnippet/index.js').ScreenSnippet,
/**
* Provides API to crash the renderer process that calls this function
* Is only used for demos.
*/
crashRendererProcess: function () {
// For practical purposes, we don't allow
// this method to work in non-dev environments
if (!process.env.ELECTRON_DEV) {
return;
}
process.crash();
},
/**
* Provides api for client side searching
@ -198,7 +197,7 @@ function createAPI() {
* this registration func is invoked then the protocolHandler callback
* will be immediately called.
*/
registerProtocolHandler: function (protocolHandler) {
registerProtocolHandler: function(protocolHandler) {
if (typeof protocolHandler === 'function') {
local.processProtocolAction = protocolHandler;
@ -343,6 +342,12 @@ function createAPI() {
});
local.ipcRenderer.on('register-crash-reporter', (event, arg) => {
if (arg) {
crashReporter.start({companyName: arg.companyName, submitURL: arg.submitURL, uploadToServer: arg.uploadToServer, extra: {'process': arg.process, podUrl: arg.podUrl}});
}
});
function updateOnlineStatus() {
local.ipcRenderer.send(apiName, {
cmd: apiCmds.isOnline,
@ -354,4 +359,4 @@ function createAPI() {
window.addEventListener('online', updateOnlineStatus, false);
updateOnlineStatus();
}
}

View File

@ -43,6 +43,10 @@ function setProtocolUrl(uri) {
protocolUrl = uri;
}
/**
* gets the protocol url set against an instance
* @returns {*}
*/
function getProtocolUrl() {
return protocolUrl;
}

View File

@ -10,10 +10,12 @@ const path = require('path');
const { isMac, isDevEnv } = require('../utils/misc.js');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const eventEmitter = require('.././eventEmitter');
// static ref to child process, only allow one screen snippet at time, so
// hold ref to prev, so can kill before starting next snippet.
let child;
let isAlwaysOnTop;
/**
* Captures a user selected portion of the monitor and returns jpeg image
@ -47,21 +49,32 @@ class ScreenSnippet {
// utilize Mac OSX built-in screencapture tool which has been
// available since OSX ver 10.2.
captureUtil = '/usr/sbin/screencapture';
captureUtilArgs = [ '-i', '-s', '-t', 'jpg', outputFileName ];
captureUtilArgs = ['-i', '-s', '-t', 'jpg', outputFileName];
} else {
// use custom built windows screen capture tool
if (isDevEnv) {
// for dev env pick up tool from node nodules
captureUtil =
path.join(__dirname,
'../../node_modules/screen-snippet/bin/Release/ScreenSnippet.exe');
'../../node_modules/screen-snippet/bin/Release/ScreenSnippet.exe');
} else {
// for production gets installed next to exec.
let execPath = path.dirname(app.getPath('exe'));
captureUtil = path.join(execPath, 'ScreenSnippet.exe');
}
captureUtilArgs = [ outputFileName ];
// Method to verify and disable always on top property
// as an issue with the ScreenSnippet.exe not being on top
// of the electron wrapper
const windows = electron.BrowserWindow.getAllWindows();
if (windows && windows.length > 0) {
isAlwaysOnTop = windows[ 0 ].isAlwaysOnTop();
if (isAlwaysOnTop) {
eventEmitter.emit('isAlwaysOnTop', false);
}
}
captureUtilArgs = [outputFileName];
}
log.send(logLevels.INFO, 'ScreenSnippet: starting screen capture util: ' + captureUtil + ' with args=' + captureUtilArgs);
@ -72,6 +85,10 @@ class ScreenSnippet {
}
child = childProcess.execFile(captureUtil, captureUtilArgs, (error) => {
// Method to reset always on top feature
if (isAlwaysOnTop) {
eventEmitter.emit('isAlwaysOnTop', true);
}
// will be called when child process exits.
if (error && error.killed) {
// processs was killed, just resolve with no data.
@ -84,9 +101,15 @@ class ScreenSnippet {
}
}
// this function was moved outside of class since class is exposed to web
// client via preload API, we do NOT want web client to be able to call this
// method - then they could read any file on the disk!
/**
* this function was moved outside of class since class is exposed to web
* client via preload API, we do NOT want web client to be able to call this
* method - then they could read any file on the disk!
* @param outputFileName
* @param resolve
* @param reject
* @param childProcessErr
*/
function readResult(outputFileName, resolve, reject, childProcessErr) {
fs.readFile(outputFileName, (readErr, data) => {
if (readErr) {
@ -120,8 +143,7 @@ function readResult(outputFileName, resolve, reject, childProcessErr) {
});
} catch (error) {
reject(createError(error));
}
finally {
} finally {
// remove tmp file (async)
fs.unlink(outputFileName, function(removeErr) {
// note: node complains if calling async
@ -136,14 +158,24 @@ function readResult(outputFileName, resolve, reject, childProcessErr) {
}
/* eslint-disable class-methods-use-this */
/**
* Create an error object with the ERROR level
* @param msg
* @returns {Error}
*/
function createError(msg) {
var err = new Error(msg);
let err = new Error(msg);
err.type = 'ERROR';
return err;
}
/**
* Create an error object with the WARN level
* @param msg
* @returns {Error}
*/
function createWarn(msg) {
var err = new Error(msg);
let err = new Error(msg);
err.type = 'WARN';
return err;
}
@ -153,4 +185,4 @@ module.exports = {
ScreenSnippet: ScreenSnippet,
// note: readResult only exposed for testing purposes
readResult: readResult
}
};

View File

@ -5,6 +5,9 @@ const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('
class SpellCheckHelper {
/**
* A constructor to create an instance of the spell checker
*/
constructor() {
this.spellCheckHandler = new SpellCheckHandler();
}

View File

@ -7,7 +7,7 @@ const logLevels = require('../enums/logLevels.js');
* Search given argv for argName using exact match or starts with.
* @param {Array} argv Array of strings
* @param {String} argName Arg name to search for.
* @param {bool} exactMatch If true then look for exact match otherwise
* @param {Boolean} exactMatch If true then look for exact match otherwise
* try finding arg that starts with argName.
* @return {String} If found, returns the arg, otherwise null.
*/
@ -26,4 +26,5 @@ function getCmdLineArg(argv, argName, exactMatch) {
return null;
}
module.exports = getCmdLineArg
module.exports = getCmdLineArg;

View File

@ -7,12 +7,11 @@
* @return {String} guid value in string
*/
function getGuid() {
const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
function(c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
function (c) {
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return guid;
}
module.exports = getGuid;

View File

@ -5,17 +5,17 @@ const { isMac } = require('./misc.js');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
var Registry = require('winreg');
var symphonyRegistryHKCU = new Registry({
let Registry = require('winreg');
let symphonyRegistryHKCU = new Registry({
hive: Registry.HKCU,
key: symphonyRegistry
});
var symphonyRegistryHKLM = new Registry({
let symphonyRegistryHKLM = new Registry({
key: symphonyRegistry
});
var symphonyRegistryHKLM6432 = new Registry({
let symphonyRegistryHKLM6432 = new Registry({
key: symphonyRegistry.replace('\\Software','\\Software\\WOW6432Node')
});
@ -24,24 +24,24 @@ var symphonyRegistryHKLM6432 = new Registry({
* that are intended to be used as global (or default) value for all users
* running this app.
*/
var getRegistry = function (name) {
var promise = new Promise(function(resolve, reject) {
let getRegistry = function (name) {
return new Promise(function (resolve, reject) {
if (isMac) {
reject('registry is not supported for mac osx.');
return;
}
//Try to get registry on HKEY_CURRENT_USER
symphonyRegistryHKCU.get( name, function( err1, reg1 ) {
if (!err1 && reg1 !==null && reg1.value) {
symphonyRegistryHKCU.get(name, function (err1, reg1) {
if (!err1 && reg1 !== null && reg1.value) {
log.send(logLevels.WARN, 'getRegistry: Cannot find ' + name + ' Registry. Using HKCU');
resolve(reg1.value);
return;
}
//Try to get registry on HKEY_LOCAL_MACHINE
symphonyRegistryHKLM.get( name, function( err2, reg2 ) {
if ( !err2 && reg2!==null && reg2.value) {
symphonyRegistryHKLM.get(name, function (err2, reg2) {
if (!err2 && reg2 !== null && reg2.value) {
log.send(logLevels.WARN, 'getRegistry: Cannot find ' + name + ' Registry. Using HKLM');
resolve(reg2.value);
return;
@ -49,18 +49,16 @@ var getRegistry = function (name) {
// Try to get registry on HKEY_LOCAL_MACHINE in case 32bit app installed on 64bit system.
// winreg does not merge keys as normally windows does.
symphonyRegistryHKLM6432.get( name, function( err3, reg3 ) {
if ( !err3 && reg3!==null && reg3.value) {
symphonyRegistryHKLM6432.get(name, function (err3, reg3) {
if (!err3 && reg3 !== null && reg3.value) {
resolve(reg3.value);
} else{
} else {
reject('Cannot find PodUrl Registry. Using default url.');
}
});
});
});
});
};
return promise;
}
module.exports = getRegistry
module.exports = getRegistry;

View File

@ -1,4 +1,4 @@
'use strict'
'use strict';
const electron = require('electron');
@ -6,8 +6,8 @@ const electron = require('electron');
/**
* Returns true if given rectangle is contained within the workArea of at
* least one of the screens.
* @param {x: Number, y: Number, width: Number, height: Number} rect
* @return {Boolean} true if condition in desc is met.
* @param {Object} rect - ex:- {x: Number, y: Number, width: Number, height: Number}
* @return {Boolean} true if condition in desc is met.
*/
function isInDisplayBounds(rect) {
if (!rect) {

View File

@ -2,6 +2,7 @@
const electron = require('electron');
const app = electron.app;
const crashReporter = electron.crashReporter;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const nodeURL = require('url');
@ -16,13 +17,13 @@ const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');
const notify = require('./notify/electron-notify.js');
const eventEmitter = require('./eventEmitter');
const throttle = require('./utils/throttle.js');
const { getConfigField, updateConfigField } = require('./config.js');
const { isMac, isNodeEnv } = require('./utils/misc');
// show dialog when certificate errors occur
require('./dialogs/showCertError.js');
require('./dialogs/showBasicAuth.js');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@ -40,20 +41,42 @@ let sandboxed = false;
const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
const MIN_WIDTH = 300;
const MIN_HEIGHT = 600;
const MIN_HEIGHT = 300;
// Default window size for pop-out windows
const DEFAULT_WIDTH = 300;
const DEFAULT_HEIGHT = 600;
/**
* Adds a window key
* @param key
* @param browserWin
*/
function addWindowKey(key, browserWin) {
windows[key] = browserWin;
}
/**
* Removes a window key
* @param key
*/
function removeWindowKey(key) {
delete windows[key];
}
/**
* Gets the parsed url
* @param url
* @returns {Url}
*/
function getParsedUrl(url) {
return nodeURL.parse(url);
}
/**
* Creates the main window
* @param initialUrl
*/
function createMainWindow(initialUrl) {
getConfigField('mainWinPos').then(
function (bounds) {
@ -63,9 +86,14 @@ function createMainWindow(initialUrl) {
// failed, use default bounds
doCreateMainWindow(initialUrl, null);
}
)
);
}
/**
* Creates the main window with bounds
* @param initialUrl
* @param initialBounds
*/
function doCreateMainWindow(initialUrl, initialBounds) {
let url = initialUrl;
let key = getGuid();
@ -154,7 +182,27 @@ function doCreateMainWindow(initialUrl, initialBounds) {
mainWindow.webContents.on('did-fail-load', function (event, errorCode,
errorDesc, validatedURL) {
loadErrors.showLoadFailure(mainWindow, validatedURL, errorDesc, errorCode, retry);
loadErrors.showLoadFailure(mainWindow, validatedURL, errorDesc, errorCode, retry, false);
});
// In case a renderer process crashes, provide an
// option for the user to either reload or close the window
mainWindow.webContents.on('crashed', function () {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash. Please reload or close this window.',
buttons: ['Reload', 'Close']
};
electron.dialog.showMessageBox(options, function (index) {
if (index === 0) {
mainWindow.reload();
}
else {
mainWindow.close();
}
});
});
addWindowKey(key, mainWindow);
@ -179,7 +227,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
function destroyAllWindows() {
let keys = Object.keys(windows);
for (var i = 0, len = keys.length; i < len; i++) {
for (let i = 0, len = keys.length; i < len; i++) {
let winKey = keys[i];
removeWindowKey(winKey);
}
@ -208,15 +256,22 @@ function doCreateMainWindow(initialUrl, initialBounds) {
});
});
// bug in electron is preventing this from working in sandboxed evt...
// https://github.com/electron/electron/issues/8841
mainWindow.webContents.on('will-navigate', function(event, willNavUrl) {
if (!sandboxed) {
return;
}
event.preventDefault();
openUrlInDefaultBrower(willNavUrl);
});
getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);
function initializeCrashReporter(podUrl) {
getConfigField('crashReporter')
.then((crashReporterConfig) => {
log.send(logLevels.INFO, 'Initializing crash reporter on the main window!');
crashReporter.start({companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: {'process': 'renderer / main window', podUrl: podUrl}});
log.send(logLevels.INFO, 'initialized crash reporter on the main window!');
mainWindow.webContents.send('register-crash-reporter', {companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, process: 'preload script / main window renderer'});
})
.catch((err) => {
log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the main window. Error is -> ' + err);
});
}
// open external links in default browser - a tag with href='_blank' or window.open
mainWindow.webContents.on('new-window', function (event, newWinUrl,
@ -243,11 +298,11 @@ function doCreateMainWindow(initialUrl, initialBounds) {
let x = 0;
let y = 0;
let width = newWinOptions.width || MIN_WIDTH;
let height = newWinOptions.height || MIN_HEIGHT;
let width = newWinOptions.width || DEFAULT_WIDTH;
let height = newWinOptions.height || DEFAULT_HEIGHT;
// try getting x and y position from query parameters
var query = newWinParsedUrl && querystring.parse(newWinParsedUrl.query);
let query = newWinParsedUrl && querystring.parse(newWinParsedUrl.query);
if (query && query.x && query.y) {
let newX = Number.parseInt(query.x, 10);
let newY = Number.parseInt(query.y, 10);
@ -273,8 +328,8 @@ function doCreateMainWindow(initialUrl, initialBounds) {
/* eslint-disable no-param-reassign */
newWinOptions.x = x;
newWinOptions.y = y;
newWinOptions.width = Math.max(width, MIN_WIDTH);
newWinOptions.height = Math.max(height, MIN_HEIGHT);
newWinOptions.width = Math.max(width, DEFAULT_WIDTH);
newWinOptions.height = Math.max(height, DEFAULT_HEIGHT);
newWinOptions.minWidth = MIN_WIDTH;
newWinOptions.minHeight = MIN_HEIGHT;
newWinOptions.alwaysOnTop = alwaysOnTop;
@ -292,36 +347,100 @@ function doCreateMainWindow(initialUrl, initialBounds) {
if (browserWin) {
log.send(logLevels.INFO, 'loaded pop-out window url: ' + newWinParsedUrl);
getConfigField('url')
.then((podUrl) => {
getConfigField('crashReporter')
.then((crashReporterConfig) => {
crashReporter.start({companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: {'process': 'renderer / child window', podUrl: podUrl}});
log.send(logLevels.INFO, 'initialized crash reporter on a child window!');
browserWin.webContents.send('register-crash-reporter', {companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, process: 'preload script / child window renderer'});
})
.catch((err) => {
log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the child window. Error is -> ' + err);
});
})
.catch(app.quit);
browserWin.winName = frameName;
browserWin.setAlwaysOnTop(alwaysOnTop);
browserWin.once('closed', function () {
let handleChildWindowClosed = () => {
removeWindowKey(newWinKey);
browserWin.removeListener('move', throttledBoundsChange);
browserWin.removeListener('resize', throttledBoundsChange);
browserWin.removeListener('resize', throttledBoundsChange);
};
browserWin.once('closed', () => {
handleChildWindowClosed();
});
browserWin.on('close', () => {
browserWin.webContents.removeListener('new-window', handleChildNewWindowEvent);
browserWin.webContents.removeListener('crashed', handleChildWindowCrashEvent);
});
let handleChildWindowCrashEvent = () => {
const options = {
type: 'error',
title: 'Renderer Process Crashed',
message: 'Oops! Looks like we have had a crash. Please reload or close this window.',
buttons: ['Reload', 'Close']
};
electron.dialog.showMessageBox(options, function (index) {
if (index === 0) {
browserWin.reload();
}
else {
browserWin.close();
}
});
};
browserWin.webContents.on('crashed', handleChildWindowCrashEvent);
let handleChildNewWindowEvent = (childEvent, childWinUrl) => {
childEvent.preventDefault();
openUrlInDefaultBrowser(childWinUrl);
};
// In case we navigate to an external link from inside a pop-out,
// we open that link in an external browser rather than creating
// a new window
browserWin.webContents.on('new-window', handleChildNewWindowEvent);
addWindowKey(newWinKey, browserWin);
// Method that sends bound changes as soon
// as a new window is created
// issue https://perzoinc.atlassian.net/browse/ELECTRON-172
sendChildWinBoundsChange(browserWin);
// throttle changes so we don't flood client.
let throttledBoundsChange = throttle(1000,
sendChildWinBoundsChange.bind(null, browserWin));
browserWin.on('move', throttledBoundsChange);
browserWin.on('resize', throttledBoundsChange);
browserWin.on('resize', throttledBoundsChange);
}
});
} else {
event.preventDefault();
openUrlInDefaultBrower(newWinUrl)
openUrlInDefaultBrowser(newWinUrl);
}
});
}
/**
* Handles the event before-quit emitted by electron
*/
app.on('before-quit', function () {
willQuitApp = true;
});
/**
* Saves the main window bounds
*/
function saveMainWinBounds() {
let newBounds = getWindowSizeAndPosition(mainWindow);
@ -330,10 +449,19 @@ function saveMainWinBounds() {
}
}
/**
* Gets the main window
* @returns {*}
*/
function getMainWindow() {
return mainWindow;
}
/**
* Gets a window's size and position
* @param window
* @returns {*}
*/
function getWindowSizeAndPosition(window) {
if (window) {
let newPos = window.getPosition();
@ -353,14 +481,28 @@ function getWindowSizeAndPosition(window) {
return null;
}
/**
* Shows the main window
*/
function showMainWindow() {
mainWindow.show();
}
/**
* Tells if a window is the main window
* @param win
* @returns {boolean}
*/
function isMainWindow(win) {
return mainWindow === win;
}
/**
* Checks if the window and a key has a window
* @param win
* @param winKey
* @returns {*}
*/
function hasWindow(win, winKey) {
if (win instanceof BrowserWindow) {
let browserWin = windows[winKey];
@ -370,12 +512,16 @@ function hasWindow(win, winKey) {
return false;
}
/**
* Sets if a user is online
* @param status
*/
function setIsOnline(status) {
isOnline = status;
}
/**
* Tries finding a window we have created with given name. If founds then
* Tries finding a window we have created with given name. If found, then
* brings to front and gives focus.
* @param {String} windowName Name of target window. Note: main window has
* name 'main'.
@ -417,8 +563,12 @@ function sendChildWinBoundsChange(window) {
}
}
function openUrlInDefaultBrower(urlToOpen) {
if (urlToOpen) {
/**
* Opens an external url in the system's default browser
* @param urlToOpen
*/
function openUrlInDefaultBrowser(urlToOpen) {
if (urlToOpen) {
electron.shell.openExternal(urlToOpen);
}
}
@ -514,11 +664,8 @@ function checkExternalDisplay(appBounds) {
return false;
}
if (rightMost > bounds.x + bounds.width || bottomMost > bounds.y + bounds.height) {
return false;
}
return !(rightMost > bounds.x + bounds.width || bottomMost > bounds.y + bounds.height);
return true;
});
}

View File

@ -1,7 +1,8 @@
{
"name": "Symphony",
"productName": "Symphony",
"version": "1.0.1",
"version": "2.0.0",
"buildNumber": "",
"description": "Symphony desktop app (Foundation ODP)",
"author": "Symphony",
"main": "js/main.js",
@ -28,7 +29,9 @@
"transformIgnorePatterns": []
},
"build": {
"asarUnpack": ["node_modules/@paulcbetts/cld/build/Release/cld.node"],
"asarUnpack": [
"node_modules/@paulcbetts/cld/build/Release/cld.node"
],
"files": [
"!coverage/*",
"!installer/*",
@ -81,7 +84,7 @@
"devDependencies": {
"browserify": "^14.1.0",
"cross-env": "^3.2.4",
"electron": "1.7.5",
"electron": "1.7.8",
"electron-builder": "^13.9.0",
"electron-builder-squirrel-windows": "^12.3.0",
"electron-packager": "^8.5.2",
@ -103,11 +106,15 @@
"async.mapseries": "^0.5.2",
"auto-launch": "^5.0.1",
"electron-dl": "^1.9.0",
"electron-spellchecker": "^1.2.0",
"electron-log": "^2.2.7",
"electron-spellchecker": "^1.1.2",
"electron-squirrel-startup": "^1.0.0",
"ffi": "^2.2.0",
"filesize": "^3.5.10",
"keymirror": "0.1.1",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.difference": "^4.5.0",
"randomstring": "^1.1.5",
"ref": "^1.3.4",
"shell-path": "^2.1.0",

View File

@ -1,27 +1,32 @@
const downloadManager = require('../js/downloadManager/downloadManager');
const downloadManager = require('../js/downloadManager');
const electron = require('./__mocks__/electron');
describe('download manager', function () {
describe('Download Manager to create DOM once download is initiated', function () {
beforeEach(function () {
describe('download manager', function() {
describe('Download Manager to create DOM once download is initiated', function() {
beforeEach(function() {
global.document.body.innerHTML =
'<div id="download-main">' +
'</div>';
});
it('should inject download bar element into DOM once download is initiated', function () {
electron.ipcRenderer.send('downloadCompleted', { _id: '12345', fileName: 'test', total: 100 });
expect(document.getElementsByClassName('text-cutoff')[0].innerHTML).toBe('test');
it('should inject download bar element into DOM once download is initiated', function() {
electron.ipcRenderer.send('downloadCompleted', { _id: '12345', fileName: 'test.png', total: 100 });
expect(document.getElementsByClassName('text-cutoff')[0].innerHTML).toBe('test.png');
expect(document.getElementById('per').innerHTML).toBe('100 Downloaded');
});
it('should inject multiple download items during multiple downloads', function () {
electron.ipcRenderer.send('downloadCompleted', { _id: '12345', fileName: 'test', total: 100 });
electron.ipcRenderer.send('downloadCompleted', { _id: '67890', fileName: 'test1', total: 200 });
it('should inject multiple download items during multiple downloads', function() {
electron.ipcRenderer.send('downloadCompleted', { _id: '12345', fileName: 'test.png', total: 100 });
electron.ipcRenderer.send('downloadCompleted', { _id: '67890', fileName: 'test.png', total: 200 });
let fileNames = document.getElementsByClassName('text-cutoff');
expect(fileNames[0].innerHTML).toBe('test1');
expect(fileNames[1].innerHTML).toBe('test');
let fNames = [];
for (var i = 0; i < fileNames.length; i++) {
fNames.push(fileNames[i].innerHTML);
}
expect(fNames).toEqual(expect.arrayContaining(['test (1).png', 'test (2).png']));
expect(document.getElementById('per').innerHTML).toBe('100 Downloaded');
let downloadElements = document.getElementsByClassName('download-element');
@ -31,23 +36,23 @@ describe('download manager', function () {
});
describe('Download Manager to initiate footer', function () {
beforeEach(function () {
describe('Download Manager to initiate footer', function() {
beforeEach(function() {
global.document.body.innerHTML =
'<div id="footer" class="hidden">' +
'<div id="download-manager-footer">' +
'<div id="download-main">' +
'</div>' +
'</div>' +
'<div id="download-manager-footer">' +
'<div id="download-main">' +
'</div>' +
'</div>' +
'</div>';
});
it('should inject dom element once download is completed', function () {
it('should inject dom element once download is completed', function() {
electron.ipcRenderer.send('downloadProgress');
expect(document.getElementById('footer').classList).not.toContain('hidden');
});
it('should remove the download bar and clear up the download items', function () {
it('should remove the download bar and clear up the download items', function() {
electron.ipcRenderer.send('downloadProgress');
expect(document.getElementById('footer').classList).not.toContain('hidden');
@ -59,19 +64,19 @@ describe('download manager', function () {
});
describe('Download Manager to initiate footer', function () {
describe('Download Manager to initiate footer', function() {
beforeEach(function () {
beforeEach(function() {
global.document.body.innerHTML =
'<div id="footer" class="hidden">' +
'<div id="download-manager-footer">' +
'<div id="download-main">' +
'</div>' +
'</div>' +
'<div id="download-manager-footer">' +
'<div id="download-main">' +
'</div>' +
'</div>' +
'</div>';
});
it('should inject ul element if not found', function () {
it('should inject ul element if not found', function() {
electron.ipcRenderer.send('downloadProgress');

View File

@ -1,10 +1,9 @@
const { ScreenSnippet, readResult } = require('../js/screenSnippet/ScreenSnippet.js');
const { ScreenSnippet, readResult } = require('../js/screenSnippet');
const path = require('path');
const fs = require('fs');
const os = require('os');
const { isMac } = require('../js/utils/misc.js')
const { isMac } = require('../js/utils/misc.js');
const snippetBase64 = require('./fixtures/snippet/snippet-base64.js');
@ -27,7 +26,7 @@ function mockedExecFile(util, args, doneCallback) {
}
function copyTestFile(destFile, done) {
const testfile = path.join(__dirname ,
const testfile = path.join(__dirname,
'fixtures/snippet/ScreenSnippet.jpeg');
let reader = fs.createReadStream(testfile);
@ -43,7 +42,7 @@ function copyTestFile(destFile, done) {
function createTestFile(done) {
let tmpDir = os.tmpdir();
const testFileName = path.join(tmpDir,
'ScreenSnippet-' + Date.now() + '.jpeg');
'ScreenSnippet-' + Date.now() + '.jpeg');
copyTestFile(testFileName, function() {
done(testFileName)
@ -65,7 +64,7 @@ describe('Tests for ScreenSnippet', function() {
expect(rsp.type).toEqual('image/jpg;base64');
expect(rsp.data).toEqual(snippetBase64);
done();
};
}
});
}
@ -104,7 +103,7 @@ describe('Tests for ScreenSnippet', function() {
// skip test for windows - until feature is supported
if (isMac) {
it('should fail if read file fails', function(done) {
var origFsReadFile = fs.readFile;
const origFsReadFile = fs.readFile;
fs.readFile = jest.fn(mockedReadFile);
@ -132,4 +131,4 @@ describe('Tests for ScreenSnippet', function() {
}
});
}
});
});

View File

@ -29,7 +29,7 @@ const ipcMain = {
ipcEmitter.on(event, cb);
},
send: function (event, args) {
var senderEvent = {
const senderEvent = {
sender: {
send: function (event, arg) {
ipcEmitter.emit(event, arg);
@ -45,16 +45,16 @@ const ipcRenderer = {
let listeners = ipcEmitter.listeners(event);
if (listeners.length > 0) {
let listener = listeners[0];
var eventArg = {};
const eventArg = {};
listener(eventArg, args);
return eventArg.returnValue;
}
return null;
},
send: function(event, args) {
var senderEvent = {
const senderEvent = {
sender: {
send: function(event, arg) {
send: function (event, arg) {
ipcEmitter.emit(event, arg);
}
}

View File

@ -3,29 +3,29 @@ const childProcess = require('child_process');
let activityDetection;
describe('Tests for Activity Detection', function () {
describe('Tests for Activity Detection', function() {
var originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
const originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
beforeAll(function (done) {
childProcess.exec(`npm rebuild --target=${process.version} --build-from-source`, function (err) {
activityDetection = require('../js/activityDetection/activityDetection.js');
beforeAll(function(done) {
childProcess.exec(`npm rebuild --target=${process.version} --build-from-source`, function(err) {
activityDetection = require('../js/activityDetection');
activityDetection.setActivityWindow(900000, electron.ipcRenderer);
done();
});
});
beforeEach(function () {
beforeEach(function() {
jest.clearAllMocks()
});
afterAll(function (done) {
afterAll(function(done) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
done();
});
it('should return null', function () {
it('should return null', function() {
activityDetection.setActivityWindow(0, electron.ipcRenderer);
const noData = activityDetection.activityDetection();
@ -33,17 +33,17 @@ describe('Tests for Activity Detection', function () {
});
it('should send activity event', function () {
it('should send activity event', function() {
const spy = jest.spyOn(activityDetection, 'send');
expect(spy).not.toBeCalled();
activityDetection.send({systemIdleTime: 120000});
expect(spy).toHaveBeenCalledWith({systemIdleTime: 120000});
activityDetection.send({ systemIdleTime: 120000 });
expect(spy).toHaveBeenCalledWith({ systemIdleTime: 120000 });
});
it('should monitor user activity', function () {
it('should monitor user activity', function() {
activityDetection.setActivityWindow(500000, electron.ipcRenderer);
const spy = jest.spyOn(activityDetection, 'monitorUserActivity');
@ -54,7 +54,7 @@ describe('Tests for Activity Detection', function () {
});
it('should not send activity event as data is undefined', function () {
it('should not send activity event as data is undefined', function() {
const spy = jest.spyOn(activityDetection, 'send');
expect(spy).not.toBeCalled();
@ -64,4 +64,4 @@ describe('Tests for Activity Detection', function () {
});
});
});

View File

@ -62,13 +62,13 @@ describe('read/write config tests', function() {
}
function createTempUserConfig(config) {
var tmpDir = os.tmpdir();
const tmpDir = os.tmpdir();
userConfigDir = fs.mkdtempSync(path.join(tmpDir, 'config-'));
return createTempConfigFile(path.join(userConfigDir, configFileName), config);
}
function createTempGlobalConfig(config) {
var tmpDir = os.tmpdir();
const tmpDir = os.tmpdir();
globalConfigDir = path.join(fs.mkdtempSync(path.join(tmpDir, 'config-')), 'config');
fs.mkdirSync(globalConfigDir);
return createTempConfigFile(path.join(globalConfigDir, configFileName), config);
@ -80,15 +80,15 @@ describe('read/write config tests', function() {
describe('getConfigField tests', function() {
it('should fail when field not present in either user or global config', function() {
var userConfig = {
const userConfig = {
url: 'something'
}
};
createTempUserConfig(userConfig);
var globalConfig = {
const globalConfig = {
url: 'something-else'
}
};
createTempGlobalConfig(globalConfig);
@ -98,9 +98,9 @@ describe('read/write config tests', function() {
});
it('should succeed when field only present in user config', function() {
var userConfig = {
const userConfig = {
url: 'something'
}
};
createTempUserConfig(userConfig);
@ -110,9 +110,9 @@ describe('read/write config tests', function() {
});
it('should succeed when field only present in global config', function() {
var globalConfig = {
const globalConfig = {
url: 'something-else'
}
};
createTempGlobalConfig(globalConfig);
@ -122,15 +122,15 @@ describe('read/write config tests', function() {
});
it('should succeed and return user config field when value is in both', function() {
var userConfig = {
const userConfig = {
url: 'something'
}
};
createTempUserConfig(userConfig);
var globalConfig = {
const globalConfig = {
url: 'something-else'
}
};
createTempGlobalConfig(globalConfig);
@ -140,7 +140,7 @@ describe('read/write config tests', function() {
});
it('should fail when global config path is invalid', function() {
var globalConfig = {
const globalConfig = {
url: 'something-else'
};
createTempGlobalConfig(globalConfig);
@ -155,7 +155,7 @@ describe('read/write config tests', function() {
});
it('should fail when user config path is invalid', function() {
var userConfig = {
const userConfig = {
url: 'something'
};
createTempUserConfig(userConfig);
@ -170,12 +170,12 @@ describe('read/write config tests', function() {
});
it('should read cached user config value rather than reading file from disk again', function(done) {
var userConfig = {
const userConfig = {
url: 'qa4.symphony.com'
};
createTempUserConfig(userConfig);
var userConfig2 = {
const userConfig2 = {
url: 'qa5.symphony.com'
};
@ -193,12 +193,12 @@ describe('read/write config tests', function() {
});
it('should read cache global config value rather than reading file from disk again', function(done) {
var globalConfig = {
const globalConfig = {
url: 'qa8.symphony.com'
};
createTempGlobalConfig(globalConfig);
var globalConfig2 = {
const globalConfig2 = {
url: 'qa9.symphony.com'
};
@ -220,7 +220,7 @@ describe('read/write config tests', function() {
describe('updateConfigField tests', function() {
it('should succeed and overwrite existing field', function() {
var userConfig = {
const userConfig = {
url: 'something'
};
@ -235,7 +235,7 @@ describe('read/write config tests', function() {
});
it('should succeed and add new field', function() {
var userConfig = {
const userConfig = {
url: 'something'
};
@ -252,7 +252,7 @@ describe('read/write config tests', function() {
it('should fail to update if invalid field name', function() {
var userConfig = {
const userConfig = {
url: 'something'
};
@ -277,7 +277,7 @@ describe('read/write config tests', function() {
it('should throw error if fieldName is not defined', function() {
var userConfig = {
const userConfig = {
url: 'something'
};
@ -291,7 +291,7 @@ describe('read/write config tests', function() {
it('should throw error if config is not defined', function() {
var userConfig = {
const userConfig = {
url: 'something'
};

View File

@ -1,5 +1,5 @@
// base64 conversion of file ScreenSnippet.jpeg
const base64ScreenSnippet =
"/9j/4AAQSkZJRgABAQEASABIAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABIAAAAAQAAAEgAAAABAAKgAgAEAAAAAQAAAEOgAwAEAAAAAQAAADsAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iAoRJQ0NfUFJPRklMRQABAQAAAnRhcHBsBAAAAG1udHJSR0IgWFlaIAfcAAsADAASADoAF2Fjc3BBUFBMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbGZJ+dk8hXeftAZKmR46dCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2Rlc2MAAAEIAAAAY2RzY20AAAFsAAA" + "ALGNwcnQAAAGYAAAALXd0cHQAAAHIAAAAFHJYWVoAAAHcAAAAFGdYWVoAAAHwAAAAFGJYWVoAAAIEAAAAFHJUUkMAAAIYAAAAEGJUUkMAAAIoAAAAEGdUUkMAAAI4AAAAEGNoYWQAAAJIAAAALGRlc2MAAAAAAAAACUhEIDcwOS1BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABAAAAAcAEgARAAgADcAMAA5AC0AQXRleHQAAAAAQ29weXJpZ2h0IEFwcGxlIENvbXB1dGVyLCBJbmMuLCAyMDEwAAAAAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAG+hAAA5IwAAA4xYWVogAAAAAAAAYpYAALe8AAAYylhZWiAAAA" + "AAAAAkngAADzsAALbOcGFyYQAAAAAAAAAAAAH2BHBhcmEAAAAAAAAAAAAB9gRwYXJhAAAAAAAAAAAAAfYEc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/wAARCAA7AEMDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFB" + "gcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwACAgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8PDw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAF/9oADAMBAAIRAxEAPwD8u6KK9e+H3gqG9Rde1ePfDn9xEw4b" + "H8bDuPQd+vTr7+CwU69RU4Hw3EXEOHyzCyxWIei0S6t9kcZongvX9dRZrWDyrduksp2qfp1J/AYrt4vhFcFczaoqN6LEWH5lh/KvbQAAABgClr7ahw3h4r31zP8Arsfznmfi9m1abeHapx6JJN/NyT/BI+RviL8CfFWqQW02gzwXrW3mZjYmJ237cbd2V7d2FfJ+r6NqugXz6ZrVpJZXUf3o5VKnB6EZ6g9iODX601xfjjwHoPj3SH0zWYgJAD5NwoHmwv2Kn09V6H8jXnZlwnTmnKg7Pt0PouFvGvFUqkaWZxU4fzJWkvOy0a8rJ/kflvRXR+LPDGpeDtfu/D2qria1bAYZ2yIeVdc9mHP6Hmucr8+qU3GTjJWaP6dw2IhWpxq0neMldPumFFFFQbH/0PzL0mwbVNUtNOU4+0yKhPoCeT+A5r67hhit4Y7eBQkcShVUdAqjAFfMPgIqPGOmh+haT8/LbH619R1+l8LUl7K" + "c+t7f195/KfjZjJvGUMP9lR5vm21/7aFeq/CzwVpPjT/hL/7WeVP7C8O6hqsHlMFzPa7NgfIOV+Y5Awfeua8FS+AodWkb4iW2o3Om+SwRdMlhhmE25dpYzI6lNu7IABzjnqD9d/Bm7/Z9f/hOv+Ed03xLFt8K6mbz7VdWj7rMeX5qxbIVxKeNpbKjnINexj8TKEHyxfqfnvDGUU8RXg6lSNtfdbd9n5fqfClFe46xefs4NpN4ug6X4pj1IwuLZri8smhE207DIFgDFA2NwBBI6Eda8OrspVXL7LXqeFjcGqLSVSMr/wAt/wBUj5Z/ae8MxXOiad4shQefZy/ZpSOpilyVz/usOP8AeNfFNfox8fvL/wCFVaxv+9uttv18+P8Apmvznr824soqOLuuqT/Nfof1h4LY6dbJeSf2JyivSyl+cmFFFFfMn62f/9H8xdO1BtJ1Sz1RBn7NKrkDuoPI/EcV9gwTxXMEdzAweKVQ6s" + "OhVhkH8RXxk67lIr1P4c+OodNVfDutybIM/uJmPCZ/gY9hnoe3TpX3HDuYxpTdKbsn+f8AwT8F8WuE6uNoQxmGjedO6aW7j5ej6dmz3+vTfhp45sfA/wDwlf263kuP7f0C+0iLy8fJLd7Nrtkj5Rt5xzXmIIYBlOQehpa+4qU1OPLLY/mrCYqdCoqtPdf8MFFFcX448eaD4C0h9T1mUGQg+TbqR5sz9go9PVug/IUVasYRc5uyQ8FgquIqxoUIuUpaJI8N/ae8TRW2iad4ThcefeS/aZQOoiiyFz/vMeP9018U10fizxPqXjHX7vxDqrZmumyFGdsaDhUXPZRx+p5rnK/Is4x/1nESqLbp6H9wcD8N/wBlZbTwktZby/xPf7tvkFFFFeYfXH//0vy7qvNCHFWKK9Q8RM2ND8a+J/DaLb2dwJrZekMw3oB6DoR9AQK7mL41Xipi40dHb1WYqPyKN/OvK2AqEquelehQzXEUl" + "ywm7ff+Z8tmXA2U4ybqYjDpye7V4t+vK1f5mv4++Pni2wggh0O2t7A3G/MjAzOu3GNu7C9+6mvljV9Z1XX759T1q7kvbqT70krFmwOgGegHYDgV9If8I3ouvD/ibW3n+R9z53XG7r90r6DrTv8AhXHgz/oH/wDkaX/4uvPx2Mr13+8ndf10PcyHh3Lsvj/slFRb3a1f3vX8T5aor6l/4Vx4M/6B/wD5Gl/+Lo/4Vx4M/wCgf/5Gl/8Ai64PZM+i9sj5aor6l/4Vx4M/6B//AJGl/wDi6P8AhXHgz/oH/wDkaX/4uj2TD2yP/9k="
"/9j/4AAQSkZJRgABAQEASABIAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABIAAAAAQAAAEgAAAABAAKgAgAEAAAAAQAAAEOgAwAEAAAAAQAAADsAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iAoRJQ0NfUFJPRklMRQABAQAAAnRhcHBsBAAAAG1udHJSR0IgWFlaIAfcAAsADAASADoAF2Fjc3BBUFBMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbGZJ+dk8hXeftAZKmR46dCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2Rlc2MAAAEIAAAAY2RzY20AAAFsAAA" + "ALGNwcnQAAAGYAAAALXd0cHQAAAHIAAAAFHJYWVoAAAHcAAAAFGdYWVoAAAHwAAAAFGJYWVoAAAIEAAAAFHJUUkMAAAIYAAAAEGJUUkMAAAIoAAAAEGdUUkMAAAI4AAAAEGNoYWQAAAJIAAAALGRlc2MAAAAAAAAACUhEIDcwOS1BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABAAAAAcAEgARAAgADcAMAA5AC0AQXRleHQAAAAAQ29weXJpZ2h0IEFwcGxlIENvbXB1dGVyLCBJbmMuLCAyMDEwAAAAAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAG+hAAA5IwAAA4xYWVogAAAAAAAAYpYAALe8AAAYylhZWiAAAA" + "AAAAAkngAADzsAALbOcGFyYQAAAAAAAAAAAAH2BHBhcmEAAAAAAAAAAAAB9gRwYXJhAAAAAAAAAAAAAfYEc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/wAARCAA7AEMDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFB" + "gcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwACAgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8PDw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAF/9oADAMBAAIRAxEAPwD8u6KK9e+H3gqG9Rde1ePfDn9xEw4b" + "H8bDuPQd+vTr7+CwU69RU4Hw3EXEOHyzCyxWIei0S6t9kcZongvX9dRZrWDyrduksp2qfp1J/AYrt4vhFcFczaoqN6LEWH5lh/KvbQAAABgClr7ahw3h4r31zP8Arsfznmfi9m1abeHapx6JJN/NyT/BI+RviL8CfFWqQW02gzwXrW3mZjYmJ237cbd2V7d2FfJ+r6NqugXz6ZrVpJZXUf3o5VKnB6EZ6g9iODX601xfjjwHoPj3SH0zWYgJAD5NwoHmwv2Kn09V6H8jXnZlwnTmnKg7Pt0PouFvGvFUqkaWZxU4fzJWkvOy0a8rJ/kflvRXR+LPDGpeDtfu/D2qria1bAYZ2yIeVdc9mHP6Hmucr8+qU3GTjJWaP6dw2IhWpxq0neMldPumFFFFQbH/0PzL0mwbVNUtNOU4+0yKhPoCeT+A5r67hhit4Y7eBQkcShVUdAqjAFfMPgIqPGOmh+haT8/LbH619R1+l8LUl7K" + "c+t7f195/KfjZjJvGUMP9lR5vm21/7aFeq/CzwVpPjT/hL/7WeVP7C8O6hqsHlMFzPa7NgfIOV+Y5Awfeua8FS+AodWkb4iW2o3Om+SwRdMlhhmE25dpYzI6lNu7IABzjnqD9d/Bm7/Z9f/hOv+Ed03xLFt8K6mbz7VdWj7rMeX5qxbIVxKeNpbKjnINexj8TKEHyxfqfnvDGUU8RXg6lSNtfdbd9n5fqfClFe46xefs4NpN4ug6X4pj1IwuLZri8smhE207DIFgDFA2NwBBI6Eda8OrspVXL7LXqeFjcGqLSVSMr/wAt/wBUj5Z/ae8MxXOiad4shQefZy/ZpSOpilyVz/usOP8AeNfFNfox8fvL/wCFVaxv+9uttv18+P8Apmvznr824soqOLuuqT/Nfof1h4LY6dbJeSf2JyivSyl+cmFFFFfMn62f/9H8xdO1BtJ1Sz1RBn7NKrkDuoPI/EcV9gwTxXMEdzAweKVQ6s" + "OhVhkH8RXxk67lIr1P4c+OodNVfDutybIM/uJmPCZ/gY9hnoe3TpX3HDuYxpTdKbsn+f8AwT8F8WuE6uNoQxmGjedO6aW7j5ej6dmz3+vTfhp45sfA/wDwlf263kuP7f0C+0iLy8fJLd7Nrtkj5Rt5xzXmIIYBlOQehpa+4qU1OPLLY/mrCYqdCoqtPdf8MFFFcX448eaD4C0h9T1mUGQg+TbqR5sz9go9PVug/IUVasYRc5uyQ8FgquIqxoUIuUpaJI8N/ae8TRW2iad4ThcefeS/aZQOoiiyFz/vMeP9018U10fizxPqXjHX7vxDqrZmumyFGdsaDhUXPZRx+p5rnK/Is4x/1nESqLbp6H9wcD8N/wBlZbTwktZby/xPf7tvkFFFFeYfXH//0vy7qvNCHFWKK9Q8RM2ND8a+J/DaLb2dwJrZekMw3oB6DoR9AQK7mL41Xipi40dHb1WYqPyKN/OvK2AqEquelehQzXEUl" + "ywm7ff+Z8tmXA2U4ybqYjDpye7V4t+vK1f5mv4++Pni2wggh0O2t7A3G/MjAzOu3GNu7C9+6mvljV9Z1XX759T1q7kvbqT70krFmwOgGegHYDgV9If8I3ouvD/ibW3n+R9z53XG7r90r6DrTv8AhXHgz/oH/wDkaX/4uvPx2Mr13+8ndf10PcyHh3Lsvj/slFRb3a1f3vX8T5aor6l/4Vx4M/6B/wD5Gl/+Lo/4Vx4M/wCgf/5Gl/8Ai64PZM+i9sj5aor6l/4Vx4M/6B//AJGl/wDi6P8AhXHgz/oH/wDkaX/4uj2TD2yP/9k=";
module.exports = base64ScreenSnippet
module.exports = base64ScreenSnippet;

View File

@ -21,7 +21,6 @@ describe('Tests for Bring to front', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
}

View File

@ -20,7 +20,6 @@ describe('Tests for clipboard', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
}

View File

@ -22,7 +22,6 @@ describe('Tests for Close', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
} else {

View File

@ -19,7 +19,6 @@ describe('Tests for Full screen', () => {
return app.startApplication().then((startedApp) => {
app = startedApp;
getConfigPath().then((config) => {
console.log(config);
configPath = config;
done();
}).catch((err) => {
@ -54,7 +53,6 @@ describe('Tests for Full screen', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
} else {
@ -99,7 +97,6 @@ describe('Tests for Full screen', () => {
app.browserWindow.focus();
return app.browserWindow.setAlwaysOnTop(true).then(() => {
return app.browserWindow.isAlwaysOnTop().then((isOnTop) => {
console.log(isOnTop);
expect(isOnTop).toBeTruthy();
});
});

View File

@ -19,7 +19,6 @@ describe('Tests for Minimize on Close', () => {
return app.startApplication().then((startedApp) => {
app = startedApp;
getConfigPath().then((config) => {
console.log(config);
configPath = config;
done();
}).catch((err) => {
@ -54,7 +53,6 @@ describe('Tests for Minimize on Close', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
} else {
@ -99,7 +97,6 @@ describe('Tests for Minimize on Close', () => {
app.browserWindow.focus();
return app.browserWindow.setAlwaysOnTop(true).then(() => {
return app.browserWindow.isAlwaysOnTop().then((isOnTop) => {
console.log(isOnTop);
expect(isOnTop).toBeTruthy();
});
});

View File

@ -23,7 +23,6 @@ describe('Tests for Notification position', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
}

View File

@ -24,7 +24,6 @@ class App {
return this.app.start().then((app) => {
return app;
}).catch((err) => {
console.log(err);
});
}

View File

@ -26,7 +26,6 @@ describe('Tests for spellChecker', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
}

View File

@ -53,7 +53,6 @@ describe('Tests for Zoom in and Zoom out', () => {
app.stop().then(() => {
done();
}).catch((err) => {
console.log(err);
done();
});
} else {
@ -111,7 +110,7 @@ describe('Tests for Zoom in and Zoom out', () => {
robot.keyToggle('0', 'up');
robot.keyToggle('command', 'up');
for (var i = 0; i < 4; i++) {
for (let i = 0; i < 4; i++) {
robot.keyToggle('+', 'down', ['command']);
}
robot.keyToggle('+', 'up');
@ -134,7 +133,7 @@ describe('Tests for Zoom in and Zoom out', () => {
robot.keyToggle('0', 'up');
robot.keyToggle('control', 'up');
for (var i = 0; i < 4; i++) {
for (let i = 0; i < 4; i++) {
robot.keyToggle('+', 'down', ['control', 'shift']);
}
robot.keyToggle('+', 'up');
@ -159,7 +158,7 @@ describe('Tests for Zoom in and Zoom out', () => {
robot.keyToggle('0', 'up');
robot.keyToggle('command', 'up');
for (var i = 0; i < 4; i++) {
for (let i = 0; i < 4; i++) {
robot.keyToggle('-', 'down', ['command']);
}
robot.keyToggle('-', 'up');
@ -183,7 +182,7 @@ describe('Tests for Zoom in and Zoom out', () => {
robot.keyToggle('0', 'up');
robot.keyToggle('control', 'up');
for (var i = 0; i < 4; i++) {
for (let i = 0; i < 4; i++) {
robot.keyToggle('-', 'down', ['control']);
}
robot.keyToggle('-', 'up');

View File

@ -2,27 +2,27 @@ const getCmdLineArg = require('../../js/utils/getCmdLineArg.js');
describe('getCmdLineArg tests', function() {
it('should return no exact match', function() {
var result = getCmdLineArg([ 'hello.exe', '--arg1', '--arg2'], '--arg', true);
const result = getCmdLineArg(['hello.exe', '--arg1', '--arg2'], '--arg', true);
expect(result).toBe(null);
});
it('should return exact match only', function() {
var result = getCmdLineArg([ 'hello.exe', '--arg1', '--arg2'], '--arg2', true);
const result = getCmdLineArg(['hello.exe', '--arg1', '--arg2'], '--arg2', true);
expect(result).toBe('--arg2');
});
it('should return starts with match', function() {
var result = getCmdLineArg([ 'hello.exe', '--hello=test', '--arg2'], '--hello=');
const result = getCmdLineArg(['hello.exe', '--hello=test', '--arg2'], '--hello=', false);
expect(result).toBe('--hello=test');
});
it('should return no match for starts with', function() {
var result = getCmdLineArg([ 'hello.exe', '--hello=test', '--arg2'], '--help=');
const result = getCmdLineArg(['hello.exe', '--hello=test', '--arg2'], '--help=', false);
expect(result).toBe(null);
});
it('should return no match invalid argv given', function() {
var result = getCmdLineArg('invalid argv', '--help=');
const result = getCmdLineArg('invalid argv', '--help=', false);
expect(result).toBe(null);
});
});

View File

@ -2,9 +2,9 @@ const getGuid = require('../../js/utils/getGuid.js');
describe('guid tests', function() {
it('should have valid length', function() {
var guid = getGuid();
const guid = getGuid();
expect(guid.length).toBe(36);
var parts = guid.split('-');
const parts = guid.split('-');
expect(parts.length).toBe(5);
expect(parts[0].length).toBe(8);
expect(parts[1].length).toBe(4);
@ -14,9 +14,9 @@ describe('guid tests', function() {
});
it('should only contains hex chars', function() {
for(var i = 0; i < 100; i++) {
var guid = getGuid();
var parts = guid.split('-');
for(let i = 0; i < 100; i++) {
const guid = getGuid();
const parts = guid.split('-');
parts.forEach(function(part) {
expect(/^([A-Fa-f0-9]{2})+$/.test(part)).toBe(true);
});

View File

@ -33,12 +33,12 @@ describe('isInDisplayBounds should', function() {
}
}]);
var rect = {
const rect = {
x: 1,
y: 1,
width: 90,
height: 90
}
};
expect(isInDisplayBounds(rect)).toBe(true);
});
@ -53,12 +53,12 @@ describe('isInDisplayBounds should', function() {
}
}]);
var rect = {
const rect = {
x: 0,
y: 0,
width: 100,
height: 100
}
};
expect(isInDisplayBounds(rect)).toBe(true);
});
@ -73,12 +73,12 @@ describe('isInDisplayBounds should', function() {
}
}]);
var rect = {
const rect = {
x: 0,
y: 0,
width: 100,
height: 100
}
};
expect(isInDisplayBounds(rect)).toBe(true);
});
@ -100,15 +100,15 @@ describe('isInDisplayBounds should', function() {
x: 100,
y: 0
}
}
};
createMockDisplay([ display1, display2 ]);
var rect = {
const rect = {
x: 110,
y: 0,
width: 50,
height: 50
}
};
expect(isInDisplayBounds(rect)).toBe(true);
});
@ -123,12 +123,12 @@ describe('isInDisplayBounds should', function() {
}
}]);
var rect = {
const rect = {
x: 0,
y: 0,
width: 100,
height: 101
}
};
expect(isInDisplayBounds(rect)).toBe(false);
});
@ -149,15 +149,15 @@ describe('isInDisplayBounds should', function() {
x: 100,
y: 0
}
}
};
createMockDisplay([ display1, display2 ]);
var rect = {
const rect = {
x: 50,
y: 50,
width: 75,
height: 25
}
};
expect(isInDisplayBounds(rect)).toBe(false);
});

View File

@ -1,7 +1,7 @@
const throttle = require('../../js/utils/throttle.js');
describe('throttle tests', function() {
var now, origNow;
let now, origNow;
beforeEach(function() {
origNow = Date.now;
// mock date func