async function onReadyAsync(optionalWidth, optionalHeight) { // this function is run when the DOM for the page has loaded // summary // if the current protocol is http rather than https, redirect to https // if login features are enabled, show login and logout buttons as appropriate // otherwise hide login and logout buttons // assign click handlers to buttons const logHdr = 'Lenses.onReadyAsync: '; log(logHdr + 'enter'); log(logHdr + 'optionalWidth=' + optionalWidth); log(logHdr + 'optionalHeight=' + optionalHeight); const PAGE_URL = urlUtils.getUrl('/lenses.html'); // get a URL for this page, to use for redirects log(logHdr + 'PAGE_URL=' + PAGE_URL); const BUY_NOW_URL = 'https://www.invisiblesolutionstools.com/buy-it'; var authenticated = false; var auth0Client = null; var theProtocol = location.protocol; // get the protocol (http or https) for the current URL log(logHdr + 'theProtocol=' + theProtocol); // if the current protocol is http, redirect to https // most mobile browsers will not allow access to location services without https if (theProtocol === 'http:') { log(logHdr + 'protocal is http, so we will set location.href to redirect to https'); log(logHdr + 'about to set location.href'); location.href = location.href.replace(/^http:/, 'https:'); // we need HTTPS for the gelocation call // yes, it's safer to handle this server-side, but we may use Azure static websites // to host this site. these web sites do not have a redirect feature. // they do have a "require https" feature, but if that is enabled the user gets an error // and we don't get a chance to redirect. log(logHdr + 'back from setting location.href'); log(logHdr + 'leave'); return; } const urlParams = new URLSearchParams(window.location.search); // parse the query string var idProvided = securityStateUtils.getAllowed(); // has the user already provided the id? idProvided = true; // for production log(logHdr + 'idProvided=' + idProvided); if (!idProvided) { // if not log(logHdr + 'id not provided previously, so we check the query string'); var hasId = urlParams.has(QUERY_STRING_KEY_ID); // does the query string have an id value? log(logHdr + 'hasId=' + hasId); if (!hasId) { // if not, redirect location.href = SECURITY_REDIRECT_URL; // redirect return; } log(logHdr + 'the query string has an id value'); const qsId = urlParams.get(QUERY_STRING_KEY_ID); // get the id from the query string log(logHdr + 'qsId=' + qsId); if (qsId === QUERY_STRING_ID_VALUE) { // if the id is correct log(logHdr + 'storing the allowed flag, for next time'); securityStateUtils.setAllowed(true); // set the value, for next time } else { // if the id is NOT correct location.href = SECURITY_REDIRECT_URL; // redirect return; } } var hasClear = urlParams.has(QUERY_STRING_KEY_CLEAR_STORAGE); // does the query string have a clear storage value? if (hasClear) { // if so const clearValue = urlParams.get(QUERY_STRING_KEY_CLEAR_STORAGE); if (clearValue === 'true') { log('we received a clear storage command in the query string, so we will clear all local storage'); globalStateUtils.clearAll(); } } // for this function (unlike some others in the app) we need the screen dimensions info here, not later viewWidth = document.documentElement.clientWidth; viewHeight = document.documentElement.clientHeight; // store view width and height log(logHdr + 'view width=' + viewWidth + ', viewHeight=' + viewHeight); const widthDefined = typeof optionalWidth !== 'undefined'; const heightDefined = typeof optionalHeight !== 'undefined'; // did the caller pass these values? if (widthDefined && heightDefined) { // if the caller passed width and height values log(logHdr + 'the caller passed width (' + optionalWidth + ') and height (' + optionalHeight + ') values so we will use them'); viewWidth = optionalWidth; viewHeight = optionalHeight; // use the parameter values } // alert('view width=' + viewWidth + ', viewHeight=' + viewHeight); viewWide = (viewWidth > 1049); // is the view wide enough that we want to show more than one column? // the previous value we used here was 693. // the idea behind this value was that 694 can show two columns of lens cards. // however, on iPhone 6, for example, if the user rotates the phone from portrait to landscape // it's more helpful to show them more info about one card than to show two cards // and force them to click something to expand a card to see more info // iPhone 6 height in portrait mode is 667, which becomes width in landscape mode log(logHdr + 'viewWide=' + viewWide); $('#btnDebugger').click(function () { debugger; }); $('#btnBuySmScrn').click(function handleBuySmScreen() { location.href = BUY_NOW_URL; }); $('#btnBuyLgScrn').click(function handleBuyLgScreen() { location.href = BUY_NOW_URL; }); // subscribe to buy button click handlers if (enableLoginFeatures) { // if login features are enabled $('#aLogin').click(async function () { await authUtils.doLoginAsync(PAGE_URL); }); $('#btnLogout').click(async function () { await authUtils.doLogoutAsync(); }); $('#btnLoginSmScrn').click(async function () { await authUtils.doLoginAsync(PAGE_URL); }); $('#btnLoginLgScrn').click(async function () { await authUtils.doLoginAsync(PAGE_URL); }); auth0Client = authUtils._auth0Client; // read the Auth0 client, or null if (auth0Client === null) { // if we haven't yet stored in Auth0 client object const tmrCreateAuth = tmrUtils.newTimer('createAuth0Client').start(); auth0Client = await createAuth0Client({ domain: authUtils.AUTH0_DOMAIN, client_id: authUtils.AUTH0_CLIENT_ID }); // allocate an Auth0 object // if the user has already authenticated, the auth0 object will be populated with an access token and user profile tmrCreateAuth.stop(); log(logHdr + 'TIME: ' + tmrCreateAuth); authUtils.setAuth0Client(auth0Client); // store the client in our auth utilities object } authenticated = await authUtils.isUserAuthenticatedAsync(); // is the current user authenticated? // use var here because we may set the variable again below log(logHdr + 'authenticated=' + authenticated); if (!authenticated) { // if the user is not authenticated log(logHdr + 'user is NOT authenticated'); var authCode = ''; var authState = ''; var hasCode = urlParams.has(QUERY_STRING_KEY_AUTH_CODE); var hasState = urlParams.has(QUERY_STRING_KEY_AUTH_STATE); // are the Auth0 values present in the query string? // if so, Auth0 put them there before it redirected to this page log(logHdr + 'hasCode=' + hasCode + ' (in query string)'); log(logHdr + 'hasState=' + hasState + ' (in query string)'); if (hasCode && hasState) { // if both values are in the query string log(logHdr + 'the query string has values for auth values state and code, so we will store those values in cookies'); authCode = urlParams.get(QUERY_STRING_KEY_AUTH_CODE); authState = urlParams.get(QUERY_STRING_KEY_AUTH_STATE); authUtils.setAuthCode(authCode); authUtils.setAuthState(authState); // store auth code and auth state in cookies hasCode = true; hasState = true; // we have them now log(logHdr + 'atc auth0Client.handleRedirectCallback'); await auth0Client.handleRedirectCallback(); // process this data to get a token and store the token in auth0Client log(logHdr + 'bf auth0Client.handleRedirectCallback'); log(logHdr + 'atc auth0Client.isAuthenticated'); authenticated = await auth0Client.isAuthenticated(); // read the authenticated value again log(logHdr + 'bf auth0.isAuthenticated'); log(logHdr + 'authenticated=' + authenticated + ' (after call to handleRedirectCallback)'); window.history.replaceState({}, document.title, PAGE_URL); // remove the querystring parameters } } if (authenticated) { // if the user is authenticated now log(logHdr + 'atc licenseUIUtils.updtTrialMenuAsync'); await licenseUIUtils.updtTrialMenuAsync(auth0Client); // update the Trial menu log(logHdr + 'bf licenseUIUtils.updtTrialMenuAsync'); const userAccount = await licenseUtils.getUserAccountAsync(auth0Client); // get the user account data if (userAccount === null) { // if failure log(logHdr + 'getUserAccountAsync returned NULL. Should NOT happen. Leaving early'); log(logHdr + 'leave'); return; } const userStatus = userAccount.userStatus; // get the user status // possible values: // trial // registered // complimentary if (userStatus === "trial") { // if user has trial account const startDateStr = userAccount.startDate; // get the date the user's trial started as a string const startDate = new Date(startDateStr); // convert the string to a date log(logHdr + 'startDate=' + startDate); const expired = licenseUtils.hasTrialExpiredByStartDate(startDate); // given the start date, has the trial expired? log(logHdr + 'expired=' + expired); if (expired) { // if the trial has expired log(logHdr + 'trial has expired, so we hide the lenses content'); $('#divRegistered').hide(); $('#divBuyNow').show(); // hide the lenses content and show the buy now content if (viewWide) { // if larger screen log(logHdr + 'showing the large screen buy now content'); $('#divBuyNowLgScrn').show(); $('#divBuyNowSmScrn').hide(); // show the large screen buy now content } else { // if smaller screen log(logHdr + 'showing the small screen buy now content'); $('#divBuyNowSmScrn').show(); $('#divBuyNowLgScrn').hide(); // show the small screen buy now content } } else { // otherwise log(logHdr + 'trial has NOT expired, so we show the lenses content'); $('#divRegistered').show(); $('#divBuyNow').hide(); // show the lenses content and hide the buy now content } } else { // if user has paid or has free account log(logHdr + 'user status is ' + userStatus + ' so we show the lenses content'); $('#divRegistered').show(); $('#divBuyNow').hide(); // show the lenses content and hide the buy now content } } else { // otherwise, hide the trial menu $('#divTrial').hide(); } // NOTE: we need viewWidth set by the time we run the code below to decide whether to show mobile or desktop // version of content for unauthorized user. if (authenticated) { // if the user is logged in log(logHdr + 'the user is logged in'); const user = await authUtils.logUserAsync(); $('#divLogin').hide(); log(logHdr + 'hid the Login button'); if (user !== null) { authUIUtils.updateUIForUser(user.given_name, user.picture); // update the Login menu with the user's name and picture } $('#divAccount').show(); // show the account menu log(logHdr + 'showed the Account menu'); $('#divUnauth').hide(); $('#divAuth').show(); // show the lenses content and hide the login prompt } else { // if the user is NOT logged in log(logHdr + 'the user is NOT logged in'); log(logHdr + 'hide the Logout button'); $('#divAccount').hide(); // hide the account menu $('#divUnauth').show(); $('#divAuth').hide(); // show the login prompt and hide the lenses content const unAuthTabs = lensHtmlUtils.showTabsOrNot(); // should we show tabs? (i.e. is the screen wide?) log(logHdr + 'unAuthTabs=' + unAuthTabs); if (unAuthTabs) { // if user not authorized and screen is large $('#divUnauthSmScrn').hide(); $('#divUnauthLgScrn').show(); // hide small screen content and show large screen content } else { // if user is not authorized and screen is small $('#divUnauthSmScrn').show(); $('#divUnauthLgScrn').hide(); // show small screen content and hide large screen content } $('#divParent').show(); $('#divFooter').show(); // show the site return; } } else { // if login features are disabled $('#divLogin').hide(); $('#divAccount').hide(); // hide login and account menus $('#divUnauth').hide(); $('#divAuth').show(); // hide login prompt and show lenses content } log(logHdr + 'lensDTOS.length=' + lensDTOs.length); // make sure this code loaded lensUtils = new LensUtils(categoryDTOs, lensDTOs); // allocate and store lens utilities const dropDownSource = lensUtils.getLensNames(); // get the names of all the lenses var defaultLensName = ''; // the lens name to display in the combo box when it's created const findCount = lensesToFindStorageUtils.count(); // how many lenses has the user specified to find? log(logHdr + 'findCount=' + findCount); if (findCount > 0) { // if the user specified a lens to find // NOTE: for now, the feature can find only one lens const findIds = lensesToFindStorageUtils.getLensIds(); if (findIds.length > 0) { const findLensId = findIds[0]; defaultLensName = lensUtils.getLensName(findLensId); log(logHdr + 'defaultLensName=' + defaultLensName); } // if the user has specified finding a lens, show its name in the find lens drop down } var placeholder = ''; const findLensRowJQ = $('#divTopBtnsRow'); // get a jQuery object for the row var centerTopBtns = false; if (viewWidth <= 320) { // we have a media query that hides the "Find Lens" caption at this width // without the caption, the combo box may confuse the user, so we set a placeholder here. // the combo box displays the placeholder when no item is selected in the combo box placeholder = '(find a lens)'; if (findCount < 1) { // if the screen is narrow and the user hasn't found any lenses centerTopBtns = true; } } log(logHdr + 'centerTopBtns=' + centerTopBtns); if (centerTopBtns) { findLensRowJQ.removeClass('justify-content-left'); findLensRowJQ.addClass('justify-content-center'); $('#divColFindLens').css('margin-left', ''); $('#divColShowHidden').css('margin-left', ''); // when the buttons are centered, remove these left margins } else { findLensRowJQ.removeClass('justify-content-center'); findLensRowJQ.addClass('justify-content-left'); $('#divColFindLens').css('margin-left', '10px'); $('#divColShowHidden').css('margin-left', '10px'); // when the buttons are left-justified, set these left margins } findLensRowJQ.addClass('justify-content-'); // center justify the contents of the row if (findLensComponent === null) { // if the combo has not already been allocated log(logHdr + '** findLensComponent is NULL, so we will allocate the combo'); const dxdParams = { dataSource: dropDownSource, placeholder: placeholder, onInitialized: function onInitialized(e) { // when the combo box has been created const logHeader = 'onInitialized: '; log(logHeader + 'enter'); findLensComponent = e.component; // store a reference to the component log(logHeader + '** set findLensComponent'); if (defaultLensName.length > 0) { e.component.option("value", defaultLensName); // display the lens name in the drop down prevNextLensUIUtils.show(); } log(logHeader + 'leave'); }, contentTemplate: function contentTemplate(e) { log('contentTemplate: enter'); findLensList = $("<div>").dxList({ dataSource: e.component.option("dataSource"), selectionMode: "single", onSelectionChanged: async function onSelectionChanged(arg) { log('onSelectionChanged: enter'); const itemsAdded = arg.addedItems; // get the array of items added by the user const addedCount = itemsAdded.length; log('onSelectionChanged: addedCount=' + addedCount); if (addedCount < 1) { log('onSelectionChanged: no item selected'); e.component.option("value", ''); // clear the lens name in the drop down's text area log('onSelectionChanged: leave'); return; } const selItem = itemsAdded[0]; log('onSelectionChanged: selItem=' + selItem); e.component.option("value", selItem); // display the lens name in the drop down's text area e.component.close(); // close the editor const lensId = lensUtils.getLensIdByName(selItem); log('onSelectionChanged: lensId=' + lensId); if (lensId < 0) { log('onSelectionChanged: leave'); return; } prevNextLensUIUtils.show(); lensesToFindStorageUtils.clear(); // for now, every lens the user finds clears any previous one lensesToFindStorageUtils.addToLensIds(lensId); tabsCardExpStorageUtils.addToLensIds(lensId); noTabsCardExpStorageUtils.addToLensIds(lensId); // add the lens to the list of expanded lenses for both displays: tabs and no tabs prevNextLensUIUtils.showHideClearBtn(); // show the Clear button await lensUIUtils.displayCurrentLensesAsync(); // display the current set of lenses await uiUtils.handleLensCountChangedAsync(); expandCollapseBtnUtils.showHideExpandCollapseBtnsAndCol(); // show or hide the expand and collapse all buttons and their parent column as necessary log('onSelectionChanged: leave'); } }); log('contentTemplate: leave'); return findLensList; } }; $('#divDxDropDown').dxDropDownBox(dxdParams); } else { // otherwise log(logHdr + 'findLensComponent is NOT NULL, so we will NOT allocate the combo'); } $('#divParent').show(); $('#divFooter').show(); // show the site if (enableLoginFeatures) { // if login features are enabled const tmrIsAuth = tmrUtils.newTimer('isUserAuthenticatedAsync').start(); authenticated = await authUtils.isUserAuthenticatedAsync(); // is the current user authenticated? tmrIsAuth.stop(); log(logHdr + 'TIME: ' + tmrIsAuth); log(logHdr + 'authenticated=' + authenticated); if (!authenticated) { // if the user is not authenticated log(logHdr + 'user is NOT authenticated'); authCode = ''; authState = ''; hasCode = urlParams.has(QUERY_STRING_KEY_AUTH_CODE); hasState = urlParams.has(QUERY_STRING_KEY_AUTH_STATE); // are the Auth0 values present in the query string? // if so, Auth0 put them there log(logHdr + 'hasCode=' + hasCode); log(logHdr + 'hasState=' + hasState); if (hasCode && hasState) { // if both values are in the query string log(logHdr + 'the query string has values for auth values state and code, so we will store those values in cookies'); authCode = urlParams.get(QUERY_STRING_KEY_AUTH_CODE); authState = urlParams.get(QUERY_STRING_KEY_AUTH_STATE); authUtils.setAuthCode(authCode); authUtils.setAuthState(authState); // store auth code and auth state in cookies hasCode = true; hasState = true; // we have them now } if (hasCode && hasState) { // if the user has both of these query string parameters, we have data we can convert to tokens const tmrCreateAuth = tmrUtils.newTimer('createAuth0Client').start(); const auth0 = await createAuth0Client({ domain: authUtils.AUTH0_DOMAIN, client_id: authUtils.AUTH0_CLIENT_ID }); // allocate an Auth0 object // if the user has already authenticated, the auth0 object will be populated with info about the user tmrCreateAuth.stop(); log(logHdr + 'TIME: ' + tmrCreateAuth); log(logHdr + 'atc auth0.handleRedirectCallback'); await auth0.handleRedirectCallback(); // process this data log(logHdr + 'bf auth0.handleRedirectCallback'); log(logHdr + 'atc auth0.isAuthenticated'); authenticated = await auth0.isAuthenticated(); // read the authenticated value again log(logHdr + 'bf auth0.isAuthenticated'); log(logHdr + 'authenticated=' + authenticated + '(after call to handleRedirectCallback)'); window.history.replaceState({}, document.title, PAGE_URL); // remove the querystring parameters } } log(logHdr + 'after if (!authenticated) block'); if (authenticated) { // if the user is logged in log(logHdr + 'the user is logged in'); const user = await authUtils.logUserAsync(); $('#divLogin').hide(); log(logHdr + 'hid the Login button'); if (user !== null) { authUIUtils.updateUIForUser(user.given_name, user.picture); // update the Login menu with the user's name and picture } $('#divAccount').show(); // show the account menu log(logHdr + 'showed the Account menu'); } else { // if the user is NOT logged in log(logHdr + 'the user is NOT logged in'); log(logHdr + 'hide the Logout button'); $('#divAccount').hide(); // hide the account menu } } else { // if login features are disabled $('#divLogin').hide(); $('#divAccount').hide(); // hide login and account menus } // mutation-summary //$domObserver = $(document); //// get a jQuery object for the document //$domObserver.mutationSummary("connect", msCallback, [{ element: "div" }]); //// subscribe changes in the DOM relating to a div topBtnsUIUtils.showHideTopBtnsHR(); // show or hide the top buttons horizontal rule // we do this here because the show/hide calls below may not change the state of the UI objects // but the state of the horizontal rule may need to change lensExampleUtils = new LensExampleUtils(lensExamplesDict); // allocate and store a utilities object for lens examples findWhichGroupUtils.showGroupIfNec(); // show or hide the "which to find" button group as necessary findWhichGroupUtils.updateBtns(); // update buttons to indicate which is selected displayByUtils.showHideDisplayByAsNec(); // show or hide the "display by" button group as necessary var hasDisplayByValue = displayByStateUtils.hasValue(); // has a display by value been stored? if (!hasDisplayByValue) { // if no "display by" value has been stored displayByStateUtils.setDisplayAlpha(true); // display lens cards alphabetically // which takes up less horizontal space than display by group and is simpler } displayByUtils.updateBtns(); // update buttons to indicate which is selected prevNextLensUIUtils.showHideClearBtn(); // show or hide the Clear button await uiUtils.handleLensCountChangedAsync(); // NOTE: this function, onReadyAsync is called in the jQuery on ready handler // but also it the window's on resize handler. // not sure why, but on iPhone 6 device we are sometimes getting wrong results // when the user clicks the next image. lens selected is too far forward in the list. // logs show the click handler is being called more than once. // for the reason, we remove handlers before assigning them in the code below. const btnExpandJQ = $('#btnExpandAll'); btnExpandJQ.off('click'); btnExpandJQ.click(expandCollapseUtils.expandAll); const btnCollapseJQ = $('#btnCollapseAll'); btnCollapseJQ.off('click'); btnCollapseJQ.click(expandCollapseUtils.collapseAll); const imgPrevJQ = $('#imgPrevLens'); imgPrevJQ.off('click'); // remove any previous subscriptions imgPrevJQ.click(prevNextLensUIUtils.handlePrevClickAsync); const imgNextJQ = $('#imgNextLens'); imgNextJQ.off('click'); // remove any previous subscriptions imgNextJQ.click(prevNextLensUIUtils.handleNextClickAsync); const findWhichJQ = $('#divFindWhichParent :input'); findWhichJQ.off('change'); findWhichJQ.change(function () { const elemId = this.id; findWhichGroupUtils.handleFindWhichGroupClickAsync(elemId); }); // subscribe to the change handler for the "find which" button group const groupByJQ = $('#divGroupByParent :input'); groupByJQ.off('change'); groupByJQ.change(function () { const elemId = this.id; topBtnsUIUtils.handleGroupByGroupClickAsync(elemId); }); // subscribe to the change handler for the "group by" button group const hiddenJQ = $('#btnShowHidden'); hiddenJQ.off('click'); hiddenJQ.click(function () { showHiddenBtnUtils.handleShowHiddenBtnClickAsync(); }); // subscribe to click in "Show Hidden Lenses" button // NOTE: handleShowHiddenBtnClickAsync is an async function, but adding "await" before the function call breaks the page const btnClearJQ = $('#btnClearFind'); btnClearJQ.off('click'); btnClearJQ.click(function () { prevNextLensUIUtils.handleClearClickAsync(); }); // subscribe to the click event for the Clear found lens button // NOTE: handleClearClickAsync is an async function, but adding "await" before the function call breaks the page const topBtnParents = $('.div-top-btns-nav'); if (viewWide) { // if we will show more than one column of lenses topBtnParents.removeClass('justify-content-center'); topBtnParents.addClass('justify-content-left'); // left justify the buttons } else { // if we will show one column of lenses topBtnParents.removeClass('justify-content-left'); topBtnParents.addClass('justify-content-center'); // center the buttons } log(logHdr + 'atc displayCurrentLensesAsync'); await lensUIUtils.displayCurrentLensesAsync(); // display the current set of lenses log(logHdr + 'bf displayCurrentLensesAsync'); expandCollapseBtnUtils.showHideExpandCollapseBtnsAndCol(); // show or hide the expand all and collapse all buttons // and their parent column as necessary log(logHdr + 'leave'); }