diff --git a/addon/data-export.css b/addon/data-export.css index 22464c3..81d70e3 100644 --- a/addon/data-export.css +++ b/addon/data-export.css @@ -1,30 +1,77 @@ +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Light.woff2); + font-weight:300 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-LightItalic.woff2); + font-style:italic; + font-weight:300 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Regular.woff2); + font-weight:400 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Italic.woff2); + font-style:italic; + font-weight:400 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Bold.woff2); + font-weight:700 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-BoldItalic.woff2); + font-style:italic; + font-weight:700 +} * { box-sizing: border-box; } html, body, #root, [data-reactroot] { height: 100%; + line-height: 1.5; + color: #16325c; } [data-reactroot] { display: flex; flex-direction: column; } body { - font-family: Arial, Helvetica, sans-serif; - font-size: 11px; + font-family: "Salesforce Sans",Arial,sans-serif; + font-size: .8125rem; overflow: hidden; - margin-top: 0; + margin: 0; + background-color: #B0C4DF; + background-image: url('chrome-extension://__MSG_@@extension_id__/images/lightning_blue_background.png'); + background-repeat: no-repeat; + background-size: contain; + background-position: 0px 48px; } #user-info { - background-color: #1797c0; - color: white; - padding: 0 1em; - border-radius: 0 0 10px 10px; - margin-bottom: 8px; + background: #f7f9fb; + height: 48px; + display: flex; + align-items: center; + padding: 0 12px; + flex-wrap: wrap; +} +#user-info h1 { + padding: 0 6px 0 10px; +} +#user-info span{ + font-size: 1.2em; } .sf-link { background-color: rgb(6, 28, 63); border-radius: 3px; - line-height: 2em; + line-height: 1.8em; text-decoration: none; display: inline-block; padding: 2px; @@ -32,8 +79,8 @@ body { padding-right: 1em; } .sf-link svg { - width: 2em; - height: 2em; + width: 1.8em;; + height: 1.8em;; display: block; margin-left: 1px; margin-right: 1em; @@ -43,27 +90,42 @@ body { fill: white; } textarea { - display:block; + display: block; width: 100%; resize: vertical; word-wrap: normal; - font-size: 11px; + font-size: 0.9rem; + padding: 8px 10px; + border-radius: 0.25rem; + border: 1px solid #DDDBDA; } textarea[hidden] { display: none; } +.help-text { + background: #fff; + border: 1px solid #DDDBDA; + padding: 0 15px; + border-radius: 0.25rem; + margin-top: 10px; + margin-bottom: 5px; +} #query { height: 5em; - margin-top: 3px; + min-height: 5em; } #result-area { flex: 1 1 0; - margin-bottom: 8px; + margin-bottom: 12px; display: flex; flex-direction: column; + padding: 0; } .result-bar { margin-bottom: 3px; + display: flex; + align-items: center; + padding: 8px 12px; } #result-text { flex: 1 1 0; @@ -73,74 +135,179 @@ textarea[hidden] { #result-table { overflow: auto; flex: 1 1 0; + background-color: #fff; + border-top: 1px solid #DDDBDA; } .area { background-color: #F8F8F8; - padding: 3px; + padding: 8px 12px; border-radius: 5px; - border: 1px solid #E0E3E5; - border-top: 3px solid #1797C0; + border: 1px solid #DDDBDA; + margin: 12px 12px 0 12px; } h1 { - font-size: 1.2em; + font-size: 1.125rem; margin: 0px; display: inline; + color: #080707; + font-weight: 700; + line-height: 1.25; } -.action-arrow { - text-align: center; +.query-controls { + display: flex; + justify-content: space-between; + align-items: flex-end; } -.arrow-body { - background-color: green; - width: 100px; - margin: 0 auto; - padding-top: 5px; -} -.arrow-head { - border-left: 50px solid transparent; - border-right: 50px solid transparent; - border-top: 15px solid green; - width: 0; - margin: 0 auto -8px; - position: relative; +.query-controls .button-group { + margin-bottom: 8px; +} +.query-options { + margin-bottom: 8px; +} +.query-options label { + margin-right: 10px; } .area input[type="radio"], .area input[type="checkbox"] { margin: 0 2px 0 0; } .area label { - padding-left: 10px; white-space: nowrap; + cursor: pointer; } .area * { vertical-align: middle } -.query-history { - width: 9em; +.query-history-controls { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; +} +select, input[type=search] { + width: 12rem; + font-family: inherit; + padding: 5px 13px; + border: 1px solid #DDDBDA; + height: 32px; + position: relative; + border-radius: 0.25rem; +} +input[type=search] { + background-image: url('chrome-extension://__MSG_@@extension_id__/images/search.svg'); + background-repeat: no-repeat; + background-size: 1rem; + background-position: 10px 7px; + padding-left: 35px; +} +input[type="checkbox"] { + width: 1rem; + height: 1rem; +} +textarea:not([readonly]):focus, +button:active, +button:focus, +input:active, +input:focus, +select:active, +select:focus { + border: 1px solid rgb(21, 137, 238); + box-shadow: rgb(6, 28, 63) 0px 0px 3px 0px; + outline: none; + z-index: 1; +} +.button-group button:active:not(:first-child), +.button-group button:focus:not(:first-child), +.button-group input:active:not(:first-child), +.button-group input:focus:not(:first-child), +.button-group select:focus:not(:first-child), +.button-group select:focus:not(:first-child) { + margin-left: -1px; + border: 1px solid rgb(21, 137, 238); +} +button:active, +button:focus { + background-color: rgb(238, 241, 246); + color: #005fb2; +} +button:disabled, +input:disabled { + color: #dddbda; + cursor: default; +} +button:disabled:hover, +input:disabled:hover { + background-color: #fff; + color: #dddbda; +} +.highlighted { + background-color: rgb(0, 112, 210); + border-color: rgb(0, 112, 210); + color: #fff; +} +.highlighted:hover, +.highlighted:active { + background-color: rgb(0, 95, 178); + color: #fff; +} +.highlighted:disabled { + background-color: #c9c7c5; + border-color: #c9c7c5; + color: #fff; +} +.highlighted:disabled:hover { + background-color: #c9c7c5; + color: #fff; +} +option[value="null"][disabled] { + display: none; } -#export-help-btn { - float: right; - margin-top: 3px; +textarea[readonly] { + outline: none; + border-radius: 0; + border: none; + border-top: 1px solid #DDDBDA; } -.autocomplete-box { +#help-btn { + margin-left: auto; + text-decoration: none; + font-size: 1.5rem; + font-weight: 700; + color: #919191; display: flex; - white-space: nowrap; + align-items: center; } -.autocomplete-box.expanded { - white-space: normal; +#help-btn:hover .icon{ + background-color: #818181; } -.autocomplete-results { - flex: 1 1 0; - overflow: hidden; +#help-btn .icon { + display: inline-block; + width: 1.4rem; + height: 1.4rem;; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 1.4rem;; + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/help.svg'); + -webkit-mask-position: center; + background-color: #919191; } #spinner { - position: absolute; left: -15px; - top: -15px; + top: 9px; +} +.flex-right { + margin-left: auto; + display: flex; + align-items: center; } #result-area h1 { margin-right: 1em; } .cancel-btn { margin-left: 1em; + color: #c23934; +} +.cancel-btn:not(:disabled):hover, +.cancel-btn:not(:disabled):active, +.cancel-btn:not(:disabled):focus { + color: #a12b2b; } .char-btn { color: white; @@ -154,9 +321,203 @@ h1 { text-align: center; margin: 1px 0 0 3px; } -.char-btn[hidden] { +.char-btn[hidden], button[hidden], .button[hidden] { display: none; } -.result-status { - float: right; +.button-group{ + white-space: nowrap; +} +.button-group button, +.button-group .button, +.button-group select, +.button-group input { + border-left-width: 0; + border-radius: 0; + margin-right: 0 +} +/* div.button-group:not(:only-of-type) { + margin-left: 10px; +} */ +.button-group > :first-child, +.button-group > [hidden] + * { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-left-width: 1px; +} +.button-group > :last-child { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + margin-right: 10px; +} +button, .button { + border: 1px solid #DDDBDA; + height: 32px; + display: inline-block; + text-decoration: none; + background-color: white; + padding: 0 16px; + color: #0070d2; + cursor: pointer; + outline: none; + position: relative; + line-height: 32px; + border-radius: 0.25rem; + margin-right: 10px; + white-space: nowrap; +} +button:hover, .button:hover { + background-color: #F4F6F9; + color: #005fb2; +} +button.toggle { + padding: 0 11px; +} +button.toggle .button-toggle-icon, +button.toggle .button-icon { + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 1rem; + background-color: #706E6B; + display: inline-block; + width: 16px; + height: 16px; +} +button.toggle:hover .button-icon, +button.toggle:hover .button-toggle-icon { + background-color: #004487; +} +.flex-right button:last-child { + margin-right: 0; +} +button.expand .button-toggle-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/down.svg'); +} +button.contract .button-toggle-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/up.svg'); +} +button.toggle .button-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/light_bulb.svg'); +} +.autocomplete-header { + display: flex; + align-items: flex-end; + margin-top: 8px; +} +.autocomplete-header span { + font-size: 1rem; +} +.autocomplete-results { + overflow: hidden; + margin-top: 7px; + display: flex; + flex-wrap: nowrap; +} +.expanded .autocomplete-results { + flex-wrap: wrap; +} +.autocomplete-results a { + border: 1px solid #DDDBDA; + border-radius: 0.25rem; + padding: 0px 4px; + text-decoration: none; + color: #006dcc; +} +.autocomplete-results a:hover, .autocomplete-results a:active { + background-color: #eff1f5; + color: #005fb2; +} +.autocomplete-result, .autocomplete-results span{ + margin: 0 4px 4px 0; + white-space: nowrap; +} +.autocomplete-icon { + display: inline-block; + margin: -1px 2px 0 1px; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 0.95rem; + background-color: #706E6B; + width: 16px; + height: 16px; +} +.relationshipName { + font-style: italic; +} +.relationshipName .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/relate.svg'); + background-color: #0070d2; +} +.object .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/sobject.svg'); + background-color: #04844B; +} +.variable .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/variable.svg'); +} +.autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/quotation_marks.svg'); +} +.null .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/steps.svg'); +} +.fieldName .autocomplete-icon { /* default icon */ + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/question_mark.svg'); +} +.fieldName.reference .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/record_lookup.svg'); +} +.fieldName.string .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/string.svg'); +} +.fieldName.id .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/anchor.svg'); +} +.fieldName.picklist .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/picklist.svg'); +} +.fieldName.multipicklist .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/multi-picklist.svg'); +} +.fieldName.boolean .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/boolean.svg'); +} +.fieldName.phone .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/call.svg'); +} +.fieldName.textarea .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/textarea.svg'); +} +.fieldName.url .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/link.svg'); +} +.fieldName.int .autocomplete-icon, .fieldName.double .autocomplete-icon, .fieldName.long .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/number.svg'); +} +.fieldName.address .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/home.svg'); +} +.fieldName.datetime .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/date-time.svg'); +} +.fieldName.date .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/date.svg'); +} +.fieldName.currency .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/currency.svg'); +} +.fieldName.email .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/email.svg'); +} +.fieldName.location .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/checkin.svg'); +} +.fieldName.percent .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/percent.svg'); +} +.fieldName.encryptedstring .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/lock.svg'); +} +.fieldName.time .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/clock.svg'); +} +.fieldName.complexvalue .autocomplete-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/advanced_function.svg'); } \ No newline at end of file diff --git a/addon/data-export.html b/addon/data-export.html index 68d9c45..f6f604d 100644 --- a/addon/data-export.html +++ b/addon/data-export.html @@ -4,6 +4,7 @@ Data Export + diff --git a/addon/data-export.js b/addon/data-export.js index 9444430..f4e2107 100644 --- a/addon/data-export.js +++ b/addon/data-export.js @@ -79,11 +79,12 @@ class Model { this.exportStatus = "Ready"; this.exportError = null; this.exportedData = null; - this.queryHistory = new QueryHistory("insextQueryHistory", 20); + this.queryHistory = new QueryHistory("insextQueryHistory", 100); this.selectedHistoryEntry = null; this.savedHistory = new QueryHistory("insextSavedQueryHistory", 50); this.selectedSavedEntry = null; this.expandAutocomplete = false; + this.expandSavedOptions = false; this.resultsFilter = ""; this.autocompleteState = ""; this.autocompleteProgress = {}; @@ -128,6 +129,9 @@ class Model { toggleExpand() { this.expandAutocomplete = !this.expandAutocomplete; } + toggleSavedOptions() { + this.expandSavedOptions = !this.expandSavedOptions; + } showDescribeUrl() { let args = new URLSearchParams(); args.set("host", this.sfHost); @@ -295,7 +299,7 @@ class Model { } // If we are just after the "from" keyword, autocomplete the sobject name - if (query.substring(0, selStart).match(/(^|\s)from\s*$/)) { + if (query.substring(0, selStart).match(/(^|\s)from\s*$/i)) { let {globalStatus, globalDescribe} = vm.describeInfo.describeGlobal(useToolingApi); if (!globalDescribe) { switch (globalStatus) { @@ -325,10 +329,10 @@ class Model { } vm.autocompleteResults = { sobjectName: "", - title: "Objects:", + title: "Object suggestions:", results: new Enumerable(globalDescribe.sobjects) .filter(sobjectDescribe => sobjectDescribe.name.toLowerCase().includes(searchTerm.toLowerCase()) || sobjectDescribe.label.toLowerCase().includes(searchTerm.toLowerCase())) - .map(sobjectDescribe => ({value: sobjectDescribe.name, title: sobjectDescribe.label, suffix: " ", rank: 1})) + .map(sobjectDescribe => ({value: sobjectDescribe.name, title: sobjectDescribe.label, suffix: " ", rank: 1, autocompleteType: "object", dataType: ""})) .toArray() .sort(resultsSort) }; @@ -545,11 +549,11 @@ class Model { } vm.autocompleteResults = { sobjectName, - title: fieldNames + " values:", + title: fieldNames + " value suggestions:", results: new Enumerable(data.records) .map(record => record[contextValueField.field.name]) .filter(value => value) - .map(value => ({value: "'" + value + "'", title: value, suffix: " ", rank: 1})) + .map(value => ({value: "'" + value + "'", title: value, suffix: " ", rank: 1, autocompleteType: "fieldValue"})) .toArray() .sort(resultsSort) }; @@ -562,7 +566,7 @@ class Model { return; } let ar = new Enumerable(contextValueFields).flatMap(function*({field}) { - yield* field.picklistValues.map(pickVal => ({value: "'" + pickVal.value + "'", title: pickVal.label, suffix: " ", rank: 1})); + yield* field.picklistValues.map(pickVal => ({value: "'" + pickVal.value + "'", title: pickVal.label, suffix: " ", rank: 1, autocompleteType: "picklistValue", dataType: ""})); if (field.type == "boolean") { yield {value: "true", title: "true", suffix: " ", rank: 1}; yield {value: "false", title: "false", suffix: " ", rank: 1}; @@ -585,46 +589,46 @@ class Model { }; } // from http://www.salesforce.com/us/developer/docs/soql_sosl/Content/sforce_api_calls_soql_select_dateformats.htm Spring 15 - yield {value: "YESTERDAY", title: "Starts 12:00:00 the day before and continues for 24 hours.", suffix: " ", rank: 1}; - yield {value: "TODAY", title: "Starts 12:00:00 of the current day and continues for 24 hours.", suffix: " ", rank: 1}; - yield {value: "TOMORROW", title: "Starts 12:00:00 after the current day and continues for 24 hours.", suffix: " ", rank: 1}; - yield {value: "LAST_WEEK", title: "Starts 12:00:00 on the first day of the week before the most recent first day of the week and continues for seven full days. First day of the week is determined by your locale.", suffix: " ", rank: 1}; - yield {value: "THIS_WEEK", title: "Starts 12:00:00 on the most recent first day of the week before the current day and continues for seven full days. First day of the week is determined by your locale.", suffix: " ", rank: 1}; - yield {value: "NEXT_WEEK", title: "Starts 12:00:00 on the most recent first day of the week after the current day and continues for seven full days. First day of the week is determined by your locale.", suffix: " ", rank: 1}; - yield {value: "LAST_MONTH", title: "Starts 12:00:00 on the first day of the month before the current day and continues for all the days of that month.", suffix: " ", rank: 1}; - yield {value: "THIS_MONTH", title: "Starts 12:00:00 on the first day of the month that the current day is in and continues for all the days of that month.", suffix: " ", rank: 1}; - yield {value: "NEXT_MONTH", title: "Starts 12:00:00 on the first day of the month after the month that the current day is in and continues for all the days of that month.", suffix: " ", rank: 1}; - yield {value: "LAST_90_DAYS", title: "Starts 12:00:00 of the current day and continues for the last 90 days.", suffix: " ", rank: 1}; - yield {value: "NEXT_90_DAYS", title: "Starts 12:00:00 of the current day and continues for the next 90 days.", suffix: " ", rank: 1}; - yield {value: "LAST_N_DAYS:n", title: "For the number n provided, starts 12:00:00 of the current day and continues for the last n days.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_DAYS:n", title: "For the number n provided, starts 12:00:00 of the current day and continues for the next n days.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_WEEKS:n", title: "For the number n provided, starts 12:00:00 of the first day of the next week and continues for the next n weeks.", suffix: " ", rank: 1}; - yield {value: "LAST_N_WEEKS:n", title: "For the number n provided, starts 12:00:00 of the last day of the previous week and continues for the last n weeks.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_MONTHS:n", title: "For the number n provided, starts 12:00:00 of the first day of the next month and continues for the next n months.", suffix: " ", rank: 1}; - yield {value: "LAST_N_MONTHS:n", title: "For the number n provided, starts 12:00:00 of the last day of the previous month and continues for the last n months.", suffix: " ", rank: 1}; - yield {value: "THIS_QUARTER", title: "Starts 12:00:00 of the current quarter and continues to the end of the current quarter.", suffix: " ", rank: 1}; - yield {value: "LAST_QUARTER", title: "Starts 12:00:00 of the previous quarter and continues to the end of that quarter.", suffix: " ", rank: 1}; - yield {value: "NEXT_QUARTER", title: "Starts 12:00:00 of the next quarter and continues to the end of that quarter.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_QUARTERS:n", title: "Starts 12:00:00 of the next quarter and continues to the end of the nth quarter.", suffix: " ", rank: 1}; - yield {value: "LAST_N_QUARTERS:n", title: "Starts 12:00:00 of the previous quarter and continues to the end of the previous nth quarter.", suffix: " ", rank: 1}; - yield {value: "THIS_YEAR", title: "Starts 12:00:00 on January 1 of the current year and continues through the end of December 31 of the current year.", suffix: " ", rank: 1}; - yield {value: "LAST_YEAR", title: "Starts 12:00:00 on January 1 of the previous year and continues through the end of December 31 of that year.", suffix: " ", rank: 1}; - yield {value: "NEXT_YEAR", title: "Starts 12:00:00 on January 1 of the following year and continues through the end of December 31 of that year.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_YEARS:n", title: "Starts 12:00:00 on January 1 of the following year and continues through the end of December 31 of the nth year.", suffix: " ", rank: 1}; - yield {value: "LAST_N_YEARS:n", title: "Starts 12:00:00 on January 1 of the previous year and continues through the end of December 31 of the previous nth year.", suffix: " ", rank: 1}; - yield {value: "THIS_FISCAL_QUARTER", title: "Starts 12:00:00 on the first day of the current fiscal quarter and continues through the end of the last day of the fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "LAST_FISCAL_QUARTER", title: "Starts 12:00:00 on the first day of the last fiscal quarter and continues through the end of the last day of that fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "NEXT_FISCAL_QUARTER", title: "Starts 12:00:00 on the first day of the next fiscal quarter and continues through the end of the last day of that fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_FISCAL_QUARTERS:n", title: "Starts 12:00:00 on the first day of the next fiscal quarter and continues through the end of the last day of the nth fiscal quarter. The fiscal year is defined in the company profile under Setup atCompany Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "LAST_N_FISCAL_QUARTERS:n", title: "Starts 12:00:00 on the first day of the last fiscal quarter and continues through the end of the last day of the previous nth fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "THIS_FISCAL_YEAR", title: "Starts 12:00:00 on the first day of the current fiscal year and continues through the end of the last day of the fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "LAST_FISCAL_YEAR", title: "Starts 12:00:00 on the first day of the last fiscal year and continues through the end of the last day of that fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "NEXT_FISCAL_YEAR", title: "Starts 12:00:00 on the first day of the next fiscal year and continues through the end of the last day of that fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "NEXT_N_FISCAL_YEARS:n", title: "Starts 12:00:00 on the first day of the next fiscal year and continues through the end of the last day of the nth fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; - yield {value: "LAST_N_FISCAL_YEARS:n", title: "Starts 12:00:00 on the first day of the last fiscal year and continues through the end of the last day of the previous nth fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1}; + yield {value: "YESTERDAY", title: "Starts 12:00:00 the day before and continues for 24 hours.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "TODAY", title: "Starts 12:00:00 of the current day and continues for 24 hours.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "TOMORROW", title: "Starts 12:00:00 after the current day and continues for 24 hours.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_WEEK", title: "Starts 12:00:00 on the first day of the week before the most recent first day of the week and continues for seven full days. First day of the week is determined by your locale.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "THIS_WEEK", title: "Starts 12:00:00 on the most recent first day of the week before the current day and continues for seven full days. First day of the week is determined by your locale.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_WEEK", title: "Starts 12:00:00 on the most recent first day of the week after the current day and continues for seven full days. First day of the week is determined by your locale.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_MONTH", title: "Starts 12:00:00 on the first day of the month before the current day and continues for all the days of that month.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "THIS_MONTH", title: "Starts 12:00:00 on the first day of the month that the current day is in and continues for all the days of that month.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_MONTH", title: "Starts 12:00:00 on the first day of the month after the month that the current day is in and continues for all the days of that month.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_90_DAYS", title: "Starts 12:00:00 of the current day and continues for the last 90 days.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_90_DAYS", title: "Starts 12:00:00 of the current day and continues for the next 90 days.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_DAYS:n", title: "For the number n provided, starts 12:00:00 of the current day and continues for the last n days.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_DAYS:n", title: "For the number n provided, starts 12:00:00 of the current day and continues for the next n days.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_WEEKS:n", title: "For the number n provided, starts 12:00:00 of the first day of the next week and continues for the next n weeks.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_WEEKS:n", title: "For the number n provided, starts 12:00:00 of the last day of the previous week and continues for the last n weeks.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_MONTHS:n", title: "For the number n provided, starts 12:00:00 of the first day of the next month and continues for the next n months.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_MONTHS:n", title: "For the number n provided, starts 12:00:00 of the last day of the previous month and continues for the last n months.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "THIS_QUARTER", title: "Starts 12:00:00 of the current quarter and continues to the end of the current quarter.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_QUARTER", title: "Starts 12:00:00 of the previous quarter and continues to the end of that quarter.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_QUARTER", title: "Starts 12:00:00 of the next quarter and continues to the end of that quarter.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_QUARTERS:n", title: "Starts 12:00:00 of the next quarter and continues to the end of the nth quarter.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_QUARTERS:n", title: "Starts 12:00:00 of the previous quarter and continues to the end of the previous nth quarter.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "THIS_YEAR", title: "Starts 12:00:00 on January 1 of the current year and continues through the end of December 31 of the current year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_YEAR", title: "Starts 12:00:00 on January 1 of the previous year and continues through the end of December 31 of that year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_YEAR", title: "Starts 12:00:00 on January 1 of the following year and continues through the end of December 31 of that year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_YEARS:n", title: "Starts 12:00:00 on January 1 of the following year and continues through the end of December 31 of the nth year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_YEARS:n", title: "Starts 12:00:00 on January 1 of the previous year and continues through the end of December 31 of the previous nth year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "THIS_FISCAL_QUARTER", title: "Starts 12:00:00 on the first day of the current fiscal quarter and continues through the end of the last day of the fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_FISCAL_QUARTER", title: "Starts 12:00:00 on the first day of the last fiscal quarter and continues through the end of the last day of that fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_FISCAL_QUARTER", title: "Starts 12:00:00 on the first day of the next fiscal quarter and continues through the end of the last day of that fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_FISCAL_QUARTERS:n", title: "Starts 12:00:00 on the first day of the next fiscal quarter and continues through the end of the last day of the nth fiscal quarter. The fiscal year is defined in the company profile under Setup atCompany Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_FISCAL_QUARTERS:n", title: "Starts 12:00:00 on the first day of the last fiscal quarter and continues through the end of the last day of the previous nth fiscal quarter. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "THIS_FISCAL_YEAR", title: "Starts 12:00:00 on the first day of the current fiscal year and continues through the end of the last day of the fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_FISCAL_YEAR", title: "Starts 12:00:00 on the first day of the last fiscal year and continues through the end of the last day of that fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_FISCAL_YEAR", title: "Starts 12:00:00 on the first day of the next fiscal year and continues through the end of the last day of that fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "NEXT_N_FISCAL_YEARS:n", title: "Starts 12:00:00 on the first day of the next fiscal year and continues through the end of the last day of the nth fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; + yield {value: "LAST_N_FISCAL_YEARS:n", title: "Starts 12:00:00 on the first day of the last fiscal year and continues through the end of the last day of the previous nth fiscal year. The fiscal year is defined in the company profile under Setup at Company Profile | Fiscal Year.", suffix: " ", rank: 1, autocompleteType: "variable", dataType: ""}; } if (field.nillable) { - yield {value: "null", title: "null", suffix: " ", rank: 1}; + yield {value: "null", title: "null", suffix: " ", rank: 1, autocompleteType: "null", dataType: ""}; } }) .filter(res => res.value.toLowerCase().includes(searchTerm.toLowerCase()) || res.title.toLowerCase().includes(searchTerm.toLowerCase())) @@ -632,7 +636,7 @@ class Model { .sort(resultsSort); vm.autocompleteResults = { sobjectName, - title: fieldNames + (ar.length == 0 ? " values (Press Ctrl+Space):" : " values:"), + title: fieldNames + (ar.length == 0 ? " values (Press Ctrl+Space to load value suggestions):" : " values:"), results: ar }; return; @@ -653,14 +657,14 @@ class Model { } vm.autocompleteResults = { sobjectName, - title: contextSobjectDescribes.map(sobjectDescribe => sobjectDescribe.name).toArray().join(", ") + " fields:", + title: contextSobjectDescribes.map(sobjectDescribe => sobjectDescribe.name).toArray().join(", ") + " field suggestions:", results: contextSobjectDescribes .flatMap(sobjectDescribe => sobjectDescribe.fields) .filter(field => field.name.toLowerCase().includes(searchTerm.toLowerCase()) || field.label.toLowerCase().includes(searchTerm.toLowerCase())) .flatMap(function*(field) { - yield {value: field.name, title: field.label, suffix: isAfterFrom ? " " : ", ", rank: 1}; + yield {value: field.name, title: field.label, suffix: isAfterFrom ? " " : ", ", rank: 1, autocompleteType: "fieldName", dataType: field.type}; if (field.relationshipName) { - yield {value: field.relationshipName + ".", title: field.label, suffix: "", rank: 1}; + yield {value: field.relationshipName + ".", title: field.label, suffix: "", rank: 1, autocompleteType: "relationshipName", dataType: ""}; } }) .concat( @@ -668,9 +672,9 @@ class Model { .filter(fn => fn.toLowerCase().startsWith(searchTerm.toLowerCase())) .map(fn => { if (fn.includes(")")) { //Exception to easily support functions with hardcoded parameter options - return {value: fn, title: fn, suffix: "", rank: 2}; + return {value: fn, title: fn, suffix: "", rank: 2, autocompleteType: "variable", dataType: ""}; } else { - return {value: fn, title: fn + "()", suffix: "(", rank: 2}; + return {value: fn, title: fn + "()", suffix: "(", rank: 2, autocompleteType: "variable", dataType: ""}; } }) ) @@ -702,7 +706,7 @@ class Model { if (!data.done) { let pr = batchHandler(sfConn.rest(data.nextRecordsUrl, {progressHandler: vm.exportProgress})); vm.isWorking = true; - vm.exportStatus = "Exporting... Completed " + exportedData.records.length + " of " + exportedData.totalSize + " record(s)."; + vm.exportStatus = "Exporting... Completed " + exportedData.records.length + " of " + exportedData.totalSize + " record(s)"; vm.exportError = null; vm.exportedData = exportedData; vm.updatedExportedData(); @@ -719,7 +723,7 @@ class Model { return null; } vm.isWorking = false; - vm.exportStatus = "Exported " + exportedData.records.length + (exportedData.records.length != exportedData.totalSize ? " of " + exportedData.totalSize : "") + " record(s)."; + vm.exportStatus = "Exported " + exportedData.records.length + (exportedData.records.length != exportedData.totalSize ? " of " + exportedData.totalSize : "") + " record(s)"; vm.exportError = null; vm.exportedData = exportedData; vm.updatedExportedData(); @@ -856,6 +860,7 @@ class App extends React.Component { this.onClearSavedHistory = this.onClearSavedHistory.bind(this); this.onToggleHelp = this.onToggleHelp.bind(this); this.onToggleExpand = this.onToggleExpand.bind(this); + this.onToggleSavedOptions = this.onToggleSavedOptions.bind(this); this.onExport = this.onExport.bind(this); this.onCopyAsExcel = this.onCopyAsExcel.bind(this); this.onCopyAsCsv = this.onCopyAsCsv.bind(this); @@ -882,9 +887,12 @@ class App extends React.Component { } onClearHistory(e) { e.preventDefault(); - let {model} = this.props; - model.clearHistory(); - model.didUpdate(); + let r = confirm("Are you sure you want to clear the query history?"); + if (r == true) { + let {model} = this.props; + model.clearHistory(); + model.didUpdate(); + } } onSelectSavedEntry(e) { let {model} = this.props; @@ -900,14 +908,22 @@ class App extends React.Component { } onRemoveFromHistory(e) { e.preventDefault(); + let r = confirm("Are you sure you want to remove this saved query?"); let {model} = this.props; - model.removeFromHistory(); + if (r == true) { + model.removeFromHistory(); + } + model.toggleSavedOptions(); model.didUpdate(); } onClearSavedHistory(e) { e.preventDefault(); + let r = confirm("Are you sure you want to remove all saved queries?"); let {model} = this.props; - model.clearSavedHistory(); + if (r == true) { + model.clearSavedHistory(); + } + model.toggleSavedOptions(); model.didUpdate(); } onToggleHelp(e) { @@ -922,6 +938,12 @@ class App extends React.Component { model.toggleExpand(); model.didUpdate(); } + onToggleSavedOptions(e) { + e.preventDefault(); + let {model} = this.props; + model.toggleSavedOptions(); + model.didUpdate(); + } onExport() { let {model} = this.props; model.doExport(); @@ -978,7 +1000,7 @@ class App extends React.Component { } }); addEventListener("keydown", e => { - if (e.ctrlKey && e.key == "Enter") { + if (e.ctrlKey && e.key == "Enter" || e.key == "F5") { e.preventDefault(); model.doExport(); model.didUpdate(); @@ -1017,8 +1039,9 @@ class App extends React.Component { } render() { let {model} = this.props; + // console.log(model.autocompleteResults.results); + // console.log(model.autocompleteResults.sobjectName); return h("div", {}, - h("img", {id: "spinner", src: "data:image/gif;base64,R0lGODlhIAAgAPUmANnZ2fX19efn5+/v7/Ly8vPz8/j4+Orq6vz8/Pr6+uzs7OPj4/f39/+0r/8gENvb2/9NQM/Pz/+ln/Hx8fDw8P/Dv/n5+f/Sz//w7+Dg4N/f39bW1v+If/9rYP96cP8+MP/h3+Li4v8RAOXl5f39/czMzNHR0fVhVt+GgN7e3u3t7fzAvPLU0ufY1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCAAmACwAAAAAIAAgAAAG/0CTcEhMEBSjpGgJ4VyI0OgwcEhaR8us6CORShHIq1WrhYC8Q4ZAfCVrHQ10gC12k7tRBr1u18aJCGt7Y31ZDmdDYYNKhVkQU4sCFAwGFQ0eDo14VXsDJFEYHYUfJgmDAWgmEoUXBJ2pQqJ2HIpXAp+wGJluEHsUsEMefXsMwEINw3QGxiYVfQDQ0dCoxgQl19jX0tIFzAPZ2dvRB8wh4NgL4gAPuKkIEeclAArqAALAGvElIwb1ABOpFOgrgSqDv1tREOTTt0FIAX/rDhQIQGBACHgDFQxJBxHawHBFHnQE8PFaBAtQHnYsWWKAlAkrP2r0UkBkvYERXKZKwFGcPhcAKI1NMLjt3IaZzIQYUNATG4AR1LwEAQAh+QQFCAAtACwAAAAAIAAgAAAG3MCWcEgstkZIBSFhbDqLyOjoEHhaodKoAnG9ZqUCxpPwLZtHq2YBkDq7R6dm4gFgv8vx5qJeb9+jeUYTfHwpTQYMFAKATxmEhU8kA3BPBo+EBFZpTwqXdQJdVnuXD6FWngAHpk+oBatOqFWvs10VIre4t7RFDbm5u0QevrjAQhgOwyIQxS0dySIcVipWLM8iF08mJRpcTijJH0ITRtolJREhA5lG374STuXm8iXeuctN8fPmT+0OIPj69Fn51qCJioACqT0ZEAHhvmIWADhkJkTBhoAUhwQYIfGhqSAAIfkEBQgAJgAsAAAAACAAIAAABshAk3BINCgWgCRxyWwKC5mkFOCsLhPIqdTKLTy0U251AtZyA9XydMRuu9mMtBrwro8ECHnZXldYpw8HBWhMdoROSQJWfAdcE1YBfCMJYlYDfASVVSQCdn6aThR8oE4Mo6RMBnwlrK2smahLrq4DsbKzrCG2RAC4JRF5uyYjviUawiYBxSWfThJcG8VVGB0iIlYKvk0VDR4O1tZ/s07g5eFOFhGtVebmVQOsVu3uTs3k8+DPtvgiDg3C+CCAQNbugz6C1iBwuGAlCAAh+QQFCAAtACwAAAAAIAAgAAAG28CWcEgstgDIhcJgbBYnTaQUkIE6r8bpdJHAeo9a6aNwVYXPaAChOSiZ0nBAqmmJlNzx8zx6v7/zUntGCn19Jk0BBQcPgVcbhYZYAnJXAZCFKlhrVyOXdxpfWACeEQihV54lIaeongOsTqmbsLReBiO4ubi1RQy6urxEFL+5wUIkAsQjCsYtA8ojs00sWCvQI11OKCIdGFcnygdX2yIiDh4NFU3gvwHa5fDx8uXsuMxN5PP68OwCpkb59gkEx2CawIPwVlxp4EBgMxAQ9jUTIuHDvIlDLnCIWA5WEAAh+QQFCAAmACwAAAAAIAAgAAAGyUCTcEgMjAClJHHJbAoVm6S05KwuLcip1ModRLRTblUB1nIn1fIUwG672YW0uvSuAx4JedleX1inESEDBE12cXIaCFV8GVwKVhN8AAZiVgJ8j5VVD3Z+mk4HfJ9OBaKjTAF8IqusqxWnTK2tDbBLsqwetUQQtyIOGLpCHL0iHcEmF8QiElYBXB/EVSQDIyNWEr1NBgwUAtXVVrytTt/l4E4gDqxV5uZVDatW7e5OzPLz3861+CMCDMH4FCgCaO6AvmMtqikgkKdKEAAh+QQFCAAtACwAAAAAIAAgAAAG28CWcEgstkpIwChgbDqLyGhpo3haodIowHK9ZqWRwZP1LZtLqmZDhDq7S6YmyCFiv8vxJqReb9+jeUYSfHwoTQQDIRGARhNCH4SFTwgacE8XkYQsVmlPHJl1HV1We5kOGKNPoCIeqaqgDa5OqxWytqMBALq7urdFBby8vkQHwbvDQw/GAAvILQLLAFVPK1YE0QAGTycjAyRPKcsZ2yPlAhQM2kbhwY5N3OXx5U7sus3v8vngug8J+PnyrIQr0GQFQH3WnjAQcHAeMgQKGjoTEuAAwIlDEhCIGM9VEAAh+QQFCAAmACwAAAAAIAAgAAAGx0CTcEi8cCCiJHHJbAoln6RU5KwuQcip1MptOLRTblUC1nIV1fK0xG672YO0WvSulyIWedleB1inDh4NFU12aHIdGFV8G1wSVgp8JQFiVhp8I5VVCBF2fppOIXygTgOjpEwEmCOsrSMGqEyurgyxS7OtFLZECrgjAiS7QgS+I3HCCcUjlFUTXAfFVgIAn04Bvk0BBQcP1NSQs07e499OCAKtVeTkVQysVuvs1lzx48629QAPBcL1CwnCTKzLwC+gQGoLFMCqEgQAIfkEBQgALQAsAAAAACAAIAAABtvAlnBILLZESAjnYmw6i8io6CN5WqHSKAR0vWaljsZz9S2bRawmY3Q6u0WoJkIwYr/L8aaiXm/fo3lGAXx8J00VDR4OgE8HhIVPGB1wTwmPhCtWaU8El3UDXVZ7lwIkoU+eIxSnqJ4MrE6pBrC0oQQluLm4tUUDurq8RCG/ucFCCBHEJQDGLRrKJSNWBFYq0CUBTykAAlYmyhvaAOMPBwXZRt+/Ck7b4+/jTuq4zE3u8O9P6hEW9vj43kqAMkLgH8BqTwo8MBjPWIIFDJsJmZDhX5MJtQwogNjwVBAAOw==", hidden: model.spinnerCount == 0}), h("div", {id: "user-info"}, h("a", {href: model.sfLink, className: "sf-link"}, h("svg", {viewBox: "0 0 24 24"}, @@ -1026,73 +1049,97 @@ class App extends React.Component { ), " Salesforce Home" ), - " \xa0 ", - h("span", {}, model.userInfo) + h("h1", {}, "Data Export"), + h("span", {}, " / " + model.userInfo), + h("div", {className: "flex-right"}, + h("div", {id: "spinner", role: "status", className: "slds-spinner slds-spinner_small slds-spinner_inline", hidden: model.spinnerCount == 0}, + h("span", {className: "slds-assistive-text"}), + h("div", {className: "slds-spinner__dot-a"}), + h("div", {className: "slds-spinner__dot-b"}), + ), + h("a", {href: "#", id: "help-btn", title: "Export Help", onClick: this.onToggleHelp}, + h("div", {className: "icon"}) + ), + ), ), h("div", {className: "area"}, - h("h1", {}, "Export query"), - h("label", {}, - h("input", {type: "checkbox", checked: model.queryAll, onChange: this.onQueryAllChange, disabled: model.queryTooling}), - " ", - h("span", {}, "Include deleted and archived records?") - ), - h("label", {title: "With the tooling API you can query more metadata, but you cannot query regular data"}, - h("input", {type: "checkbox", checked: model.queryTooling, onChange: this.onQueryToolingChange, disabled: model.queryAll}), - " ", - h("span", {}, "Use Tooling API?") + h("div", {className: "area-header"}, + h("h1", {}, "Export Query") ), - h("label", {}, - h("select", {value: JSON.stringify(model.selectedHistoryEntry), onChange: this.onSelectHistoryEntry, className: "query-history"}, - h("option", {value: JSON.stringify(null)}, "Query history"), - model.queryHistory.list.map(q => h("option", {key: JSON.stringify(q), value: JSON.stringify(q)}, q.query.substring(0, 300))) + h("div", {className: "query-controls"}, + h("div", {className: "query-options"}, + h("label", {}, + h("input", {type: "checkbox", checked: model.queryAll, onChange: this.onQueryAllChange, disabled: model.queryTooling}), + " ", + h("span", {}, "Include deleted and archived records?") + ), + h("label", {title: "With the tooling API you can query more metadata, but you cannot query regular data"}, + h("input", {type: "checkbox", checked: model.queryTooling, onChange: this.onQueryToolingChange, disabled: model.queryAll}), + " ", + h("span", {}, "Use Tooling API?") + ), ), - h("a", {href: "about:blank", onClick: this.onClearHistory, title: "Clear query history", className: "char-btn"}, "X") - ), - h("label", {}, - h("select", {value: JSON.stringify(model.selectedSavedEntry), onChange: this.onSelectSavedEntry, className: "query-history"}, - h("option", {value: JSON.stringify(null)}, "Saved queries"), - model.savedHistory.list.map(q => h("option", {key: JSON.stringify(q), value: JSON.stringify(q)}, q.query.substring(0, 300))) + h("div", {className: "query-history-controls"}, + h("div", {className: "button-group"}, + h("select", {value: JSON.stringify(model.selectedHistoryEntry), onChange: this.onSelectHistoryEntry, className: "query-history"}, + h("option", {value: JSON.stringify(null), disabled: true}, "Query History"), + model.queryHistory.list.map(q => h("option", {key: JSON.stringify(q), value: JSON.stringify(q)}, q.query.substring(0, 300))) + ), + h("button", {onClick: this.onClearHistory, title: "Clear Query History"}, "Clear") + ), + h("div", {className: "pop-menu saveOptions", hidden: !model.expandSavedOptions}, + h("a", {href: "#", onClick: this.onRemoveFromHistory, title: "Remove query from saved history"}, "Removed Saved Query"), + h("a", {href: "#", onClick: this.onClearSavedHistory, title: "Clear saved history"}, "Clear Saved Queries") + ), + h("div", {className: "button-group"}, + h("select", {value: JSON.stringify(model.selectedSavedEntry), onChange: this.onSelectSavedEntry, className: "query-history"}, + h("option", {value: JSON.stringify(null) , disabled: true}, "Saved Queries"), + model.savedHistory.list.map(q => h("option", {key: JSON.stringify(q), value: JSON.stringify(q)}, q.query.substring(0, 300))) + ), + h("button", {onClick: this.onAddToHistory, title: "Add query to saved history"}, "Save Query"), + h("button", {className: model.expandSavedOptions ? "toggle contract" : "toggle expand", title: "Show More Options", onClick: this.onToggleSavedOptions}, h("div", {className: "button-toggle-icon"})), + ), ), - h("a", {href: "about:blank", onClick: this.onAddToHistory, title: "Add query to saved history", className: "char-btn"}, "+"), - h("a", {href: "about:blank", onClick: this.onRemoveFromHistory, title: "Remove query from saved history", className: "char-btn"}, "X"), - h("a", {href: "about:blank", onClick: this.onClearSavedHistory, title: "Clear saved history", className: "char-btn"}, "XX") ), - h("a", {href: "about:blank", id: "export-help-btn", onClick: this.onToggleHelp}, "Export help"), h("textarea", {id: "query", ref: "query", style: {maxHeight: (model.winInnerHeight - 200) + "px"}}), h("div", {className: "autocomplete-box" + (model.expandAutocomplete ? " expanded" : "")}, - h("span", {className: "autocomplete-results"}, + h("div", {className: "autocomplete-header"}, h("span", {}, model.autocompleteResults.title), - " ", + h("div", {className: "flex-right"}, + h("button", {disabled: model.isWorking, onClick: this.onExport, title: "Ctrl+Enter / F5", className: "highlighted"}, "Run Export"), + h("a", {className: "button", hidden: !model.autocompleteResults.sobjectName, href: model.showDescribeUrl(), target: "_blank", title: "Show field info for the " + model.autocompleteResults.sobjectName + " object"}, model.autocompleteResults.sobjectName + " Field Info"), + h("button", {href: "#", className: model.expandAutocomplete ? "toggle contract" : "toggle expand", onClick: this.onToggleExpand, title: "Show all suggestions or only the first line"}, + h("div", {className: "button-icon"}), + h("div", {className: "button-toggle-icon"}) + ) + ), + ), + h("div", {className: "autocomplete-results"}, model.autocompleteResults.results.map(r => - h("span", {key: r.value}, h("a", {title: r.title, onClick: e => { e.preventDefault(); model.autocompleteClick(r); model.didUpdate(); }, href: "about:blank"}, r.value), " ") + h("div", {className: "autocomplete-result", key: r.value}, h("a", {title: r.title, onClick: e => { e.preventDefault(); model.autocompleteClick(r); model.didUpdate(); }, href: "#", className:r.autocompleteType + ' ' + r.dataType}, h("div", {className: "autocomplete-icon"}), r.value), " ") ) ), - h("a", {className: "char-btn", hidden: !model.autocompleteResults.sobjectName, href: model.showDescribeUrl(), title: "Show field info for the " + model.autocompleteResults.sobjectName + " object"}, "i"), - h("a", {href: "about:blank", className: "char-btn", onClick: this.onToggleExpand, title: "Show all suggestions or only the first line"}, model.expandAutocomplete ? "-" : "+") ), - h("div", {hidden: !model.showHelp}, + h("div", {hidden: !model.showHelp, className: "help-text"}, + h("h3", {}, "Export Help"), h("p", {}, "Use for quick one-off data exports. Enter a ", h("a", {href: "http://www.salesforce.com/us/developer/docs/soql_sosl/", target: "_blank"}, "SOQL query"), " in the box above and press Export."), h("p", {}, "Press Ctrl+Space to insert all field name autosuggestions or to load suggestions for field values."), + h("p", {}, "Press Ctrl+Enter or F5 to execute the export."), h("p", {}, "Supports the full SOQL language. The columns in the CSV output depend on the returned data. Using subqueries may cause the output to grow rapidly. Bulk API is not supported. Large data volumes may freeze or crash your browser.") ) ), - h("div", {className: "action-arrow"}, - h("div", {className: "arrow-body"}, h("button", {disabled: model.isWorking, onClick: this.onExport, title: "Ctrl+Enter"}, "Export")), - h("div", {className: "arrow-head"}) - ), h("div", {className: "area", id: "result-area"}, h("div", {className: "result-bar"}, - h("h1", {}, "Export result"), - h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsExcel, title: "Copy exported data to clipboard for pasting into Excel or similar"}, "Copy (Excel format)"), - " ", - h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsCsv, title: "Copy exported data to clipboard for saving as a CSV file"}, "Copy (CSV)"), - " ", - h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsJson, title: "Copy raw API output to clipboard"}, "Copy (JSON)"), - " ", - h("input", {placeholder: "Filter results", value: model.resultsFilter, onInput: this.onResultsFilterInput}), - h("span", {className: "result-status"}, + h("h1", {}, "Export Result"), + h("div", {className: "button-group"}, + h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsExcel, title: "Copy exported data to clipboard for pasting into Excel or similar"}, "Copy (Excel format)"), + h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsCsv, title: "Copy exported data to clipboard for saving as a CSV file"}, "Copy (CSV)"), + h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsJson, title: "Copy raw API output to clipboard"}, "Copy (JSON)"), + ), + h("input", {placeholder: "Filter Results", type: "search", value: model.resultsFilter, onInput: this.onResultsFilterInput}), + h("span", {className: "result-status flex-right"}, h("span", {}, model.exportStatus), - h("button", {className: "cancel-btn", disabled: !model.isWorking, onClick: this.onStopExport}, "Stop") + h("button", {className: "cancel-btn", disabled: !model.isWorking, onClick: this.onStopExport}, "Stop"), ) ), h("textarea", {id: "result-text", readOnly: true, value: model.exportError || "", hidden: model.exportError == null}), diff --git a/addon/data-import.css b/addon/data-import.css index 656b660..5fedb29 100644 --- a/addon/data-import.css +++ b/addon/data-import.css @@ -1,16 +1,55 @@ +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Light.woff2); + font-weight:300 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-LightItalic.woff2); + font-style:italic; + font-weight:300 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Regular.woff2); + font-weight:400 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Italic.woff2); + font-style:italic; + font-weight:400 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-Bold.woff2); + font-weight:700 +} +@font-face { + font-family:'Salesforce Sans'; + src:url(fonts/SalesforceSans-BoldItalic.woff2); + font-style:italic; + font-weight:700 +} * { box-sizing: border-box; + vertical-align: middle; } html { height: 100%; } body { - font-family: Arial, Helvetica, sans-serif; - font-size: 11px; + font-family: "Salesforce Sans", Arial, sans-serif; + font-size: .8125rem; overflow: hidden; - margin: 0 8px; + margin: 0; height: 100%; display: flex; + background-color: #B0C4DF; + background-image: url('chrome-extension://__MSG_@@extension_id__/images/lightning_blue_background.png'); + background-repeat: no-repeat; + background-size: contain; + background-position: 0px 48px; } #root { display: flex; @@ -23,17 +62,27 @@ body { flex-direction: column; width: 100%; } +[hidden] { + display: none !important; +} #user-info { - background-color: #1797c0; - color: white; - padding: 0 1em; - border-radius: 0 0 10px 10px; - margin-bottom: 8px; + background: #f7f9fb; + min-height: 48px; + display: flex; + align-items: center; + padding: 0 12px; + flex-wrap: wrap; +} +#user-info h1 { + padding: 0 6px 0 10px; +} +#user-info span{ + font-size: 1.2em; } .sf-link { background-color: rgb(6, 28, 63); border-radius: 3px; - line-height: 2em; + line-height: 1.8em; text-decoration: none; display: inline-block; padding: 2px; @@ -41,8 +90,8 @@ body { padding-right: 1em; } .sf-link svg { - width: 2em; - height: 2em; + width: 1.8em;; + height: 1.8em;; display: block; margin-left: 1px; margin-right: 1em; @@ -51,6 +100,45 @@ body { border-radius: 2px; fill: white; } +#help-btn { + margin-left: auto; + text-decoration: none; + font-size: 1.5rem; + font-weight: 700; + color: #919191; + display: flex; + align-items: center; +} +#help-btn:hover .icon{ + background-color: #818181; +} +#help-btn .icon { + display: inline-block; + width: 1.4rem; + height: 1.4rem;; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 1.4rem;; + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/help.svg'); + -webkit-mask-position: center; + background-color: #919191; +} +#spinner { + left: -15px; + top: 9px; +} +.flex-right { + margin-left: auto; + display: flex; + align-items: center; +} +.help-text { + background: #fff; + border: 1px solid #DDDBDA; + padding: 0 15px; + border-radius: 0.25rem; + margin-top: 10px; + margin-bottom: 5px; +} #result-table { margin-top: 3px; overflow: auto; @@ -58,9 +146,35 @@ body { flex-grow: 1; margin-bottom: 8px; } +.area { + background-color: #F8F8F8; + padding: 8px 12px; + border-radius: 5px; + border: 1px solid #DDDBDA; + margin: 12px 12px 0 12px; + flex-grow: 1; +} +.area.configure-import { + overflow-y: auto; +} +div[data-reactroot] > .area:last-child { + margin-bottom: 12px; +} +.area-header { + margin-bottom: 12px; +} +h1 { + font-size: 1.125rem; + margin: 0px; + display: inline; + color: #080707; + font-weight: 700; + line-height: 1.25; +} .conf-section { display: flex; flex-direction: row; + max-height: calc(50% - 79px); } @media (max-width: 670px) { .conf-section { @@ -70,73 +184,86 @@ body { .conf-subsection { flex-grow: 1; flex-basis: 0; + display: flex; } -.conf-label, .columns-label { - font-weight: bold; +.conf-subsection:last-child .area { + margin-left: 0; +} +.area.import-actions { + flex-grow: 0; +} +.conf-label, .columns-label, label { + font-weight: normal; color: #4a4a56; + display: flex; + align-items: center; } .conf-label { display: inline-block; - text-align: right; - width: 90px; + text-align: left; + width: 9em; padding-right: 1em; } -@media (max-width: 670px) { - .columns-label { - display: inline-block; - text-align: right; - width: 90px; - padding-right: 1em; - } +.radio-buttons label { + margin-right: 10px; + align-items: end; } .conf-value { display: inline-block; } -.conf-line textarea, .conf-line input[type=text], .conf-line select { - width: 200px; -} .conf-line { - margin-top: 3px; + margin-top: 5px; +} +.configure-import .conf-line { + display: flex; + align-items: flex-start; } .conf-error { color: #a00; - margin: 5px; + margin-left: 12px; + display: flex; + align-items: center; +} +.conf-error button{ + margin: 0 0 0 6px; } .confError { - box-shadow: 0 0 4px 2px #a00; + box-shadow: 0 0 3px 1px #a00; +} +.columns-mapping .conf-line { + padding: 5px 0; + margin: 0; } -.columns-mapping { +.flex-wrapper { + display: flex; + align-items: center; +} +.columns-mapping label { + display: block; + margin-bottom: 2px; } .columns-mapping .conf-label { display: block; } .columns-mapping .conf-value { - overflow: auto; - display: block; + overflow-y: auto; + display: flex; + flex-direction: column; padding: 5px; - max-height: 150px; -} -.columns-mapping .conf-line input[type=text] { - width: 100%; -} -#data { - resize: none; - white-space: pre; - word-wrap: normal; - height: auto; - overflow: hidden; - text-align: center; - color: #777; - padding: 1.5em; - font: inherit; + max-height: calc(100% - 34px); } -h1 { - font-size: 1.2em; - margin: 0px; - display: inline; +.columns-mapping .conf-line input[type=search] { + flex-grow: 1; + width: auto; } .status-group { - margin-top: 3px; + display: flex; + flex-wrap: wrap; +} +.status-group div { + display: flex; + flex-wrap: wrap; + justify-content: space-between; } .status-group label { font-weight: bold; @@ -144,18 +271,6 @@ h1 { padding-right: 15px; white-space: nowrap; } -* { - vertical-align: middle; -} -#import-help-btn { - float: right; - margin-top: 3px; -} -#spinner { - position: absolute; - left: -15px; - top: -15px; -} label.statusGroupEmpty { color: lightgray; font-weight: normal; @@ -183,7 +298,6 @@ label.statusGroupEmpty { background: rgba(0,0,0,0.8); z-index: 99999; } - #confirm-dialog { width: 400px; position: relative; @@ -198,3 +312,197 @@ label.statusGroupEmpty { .button-space { margin-right: 20px; } +select, +input[type=search], +input[type=number], +input[type=text] { + width: 335px; + font-family: inherit; + padding: 5px 13px; + border: 1px solid #DDDBDA; + border-radius: 0.25rem; + height: 32px; + position: relative; +} +input.object-list { + width: 296px; +} +input[type="checkbox"] { + width: 1rem; + height: 1rem; +} +.configure-import input[type="checkbox"] { + margin: 0; +} +textarea:focus, +button:active, +button:focus, +input:active, +input:focus, +select:active, +select:focus { + border: 1px solid rgb(21, 137, 238); + box-shadow: rgb(6, 28, 63) 0px 0px 3px 0px; + outline: none; + z-index: 1; +} +.button-group button:active:not(:first-child), +.button-group button:focus:not(:first-child), +.button-group input:active:not(:first-child), +.button-group input:focus:not(:first-child), +.button-group select:focus:not(:first-child), +.button-group select:focus:not(:first-child) { + margin-left: -1px; + border: 1px solid rgb(21, 137, 238); +} +button:active, +button:focus { + background-color: rgb(238, 241, 246); + color: #005fb2; +} +button:disabled, +input:disabled { + color: #dddbda; + cursor: default; +} +button:disabled:hover { + background-color: #fff; + color: #dddbda; +} +.highlighted { + background-color: rgb(0, 112, 210); + border-color: rgb(0, 112, 210); + color: #fff; +} +.highlighted:hover, +.highlighted:active { + background-color: rgb(0, 95, 178); + color: #fff; +} +.highlighted:disabled { + background-color: #c9c7c5; + border-color: #c9c7c5; + color: #fff; +} +.highlighted:disabled:hover { + background-color: #c9c7c5; + color: #fff; +} +option[value="null"][disabled] { + display: none; +} +textarea { + outline: none; + border-radius: 0.25rem; + border: 1px solid #DDDBDA; +} +#data { + resize: none; + white-space: pre; + word-wrap: normal; + height: auto; + width: 335px; + overflow: hidden; + text-align: center; + color: #777; + padding: 1.5em; + font: inherit; +} +.import-actions .conf-line { + display: flex; + align-items: center; + margin: 0; +} +.button-group{ + white-space: nowrap; +} +.button-group button, +.button-group .button, +.button-group select, +.button-group input { + border-left-width: 0; + border-radius: 0; + margin-right: 0 +} +.button-group > :first-child, +.button-group > [hidden] + * { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + border-left-width: 1px; +} +.button-group > :last-child { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + margin-right: 10px; +} +button, .button { + border: 1px solid #DDDBDA; + height: 32px; + display: inline-block; + text-decoration: none; + background-color: white; + padding: 0 16px; + color: #0070d2; + cursor: pointer; + outline: none; + position: relative; + line-height: 32px; + border-radius: 0.25rem; + margin-right: 10px; + white-space: nowrap; +} +button:hover, .button:hover { + background-color: #F4F6F9; + color: #005fb2; +} +button.toggle { + padding: 0 11px; +} +.button.field-info { + margin: 0 0 0 5px; + padding: 0 8px; + line-height: 28px; +} +.button-toggle-icon, +.button-icon { + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 1rem; + background-color: #706E6B; + display: inline-block; + width: 16px; + height: 16px; +} +button.toggle:hover .button-icon, +button.toggle:hover .button-toggle-icon { + background-color: #004487; +} +.flex-right button:last-child { + margin-right: 0; +} +button.expand .button-toggle-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/down.svg'); +} +button.contract .button-toggle-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/up.svg'); +} +.button.field-info .button-icon { + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/salesforce-inspector-logo.svg'); +} +.cancel-btn { + color: #c23934; +} +.cancel-btn:not(:disabled):hover, +.cancel-btn:not(:disabled):active, +.cancel-btn:not(:disabled):focus { + color: #a12b2b; +} +.area.result-area { + padding: 0; + height: 40%; +} +#result-table { + overflow: auto; + background-color: #fff; + margin: 0; + height: 100%; +} \ No newline at end of file diff --git a/addon/data-import.html b/addon/data-import.html index a480846..5f7ea65 100644 --- a/addon/data-import.html +++ b/addon/data-import.html @@ -4,6 +4,7 @@ Data Import + diff --git a/addon/data-import.js b/addon/data-import.js index 57b2732..0122c04 100644 --- a/addon/data-import.js +++ b/addon/data-import.js @@ -19,6 +19,7 @@ class Model { this.useToolingApi = false; this.dataFormat = "excel"; this.importAction = "create"; + this.importActionName = "Insert"; this.importType = "Account"; this.externalId = "Id"; this.batchSize = "200"; @@ -93,7 +94,7 @@ class Model { try { data = csvParse(text, separator); } catch (e) { - console.log(e); + /* console.log(e); */ this.dataError = "Error: " + e.message; this.updateResult(null); return; @@ -119,7 +120,7 @@ class Model { return; } this.dataError = ""; - let header = data.shift().map(c => this.makeColumn(c)); + let header = data.shift().map((c, index) => this.makeColumn(c, index)); this.updateResult(null); // Two updates, the first clears state from the scrolltable this.updateResult({header, data}); } @@ -383,7 +384,7 @@ class Model { let importedRecords = this.importData.counts.Queued + this.importData.counts.Processing; let skippedRecords = this.importData.counts.Succeeded + this.importData.counts.Failed; this.confirmPopup = { - text: importedRecords + " records will be imported." + text: importedRecords + " records will be actioned." + (skippedRecords > 0 ? " " + skippedRecords + " records will be skipped because they have __Status Succeeded or Failed." : "") }; } @@ -434,11 +435,13 @@ class Model { this.updateImportTableResult(); } - makeColumn(column) { + makeColumn(column, index) { let self = this; let xmlName = /^[a-zA-Z_][a-zA-Z0-9_]*$/; // A (subset of a) valid XML name let columnVm = { + columnIndex: index, columnValue: column, + columnOriginalValue: column, columnIgnore() { return columnVm.columnValue.startsWith("_"); }, columnSkip() { columnVm.columnValue = "_" + columnVm.columnValue; @@ -669,6 +672,7 @@ class App extends React.Component { onImportActionChange(e) { let {model} = this.props; model.importAction = e.target.value; + model.importActionName = e.target.options[e.target.selectedIndex].text; model.didUpdate(); } onImportTypeChange(e) { @@ -789,8 +793,8 @@ class App extends React.Component { } render() { let {model} = this.props; + //console.log(model); return h("div", {}, - h("img", {id: "spinner", src: "data:image/gif;base64,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", hidden: model.spinnerCount == 0}), h("div", {id: "user-info"}, h("a", {href: model.sfLink, className: "sf-link"}, h("svg", {viewBox: "0 0 24 24"}, @@ -798,149 +802,181 @@ class App extends React.Component { ), " Salesforce Home" ), - " \xa0 ", h("h1", {}, "Data Import"), - h("span", {}, " / " + model.userInfo) + h("span", {}, " / " + model.userInfo), + h("div", {className: "flex-right"}, + h("div", {id: "spinner", role: "status", className: "slds-spinner slds-spinner_small slds-spinner_inline", hidden: model.spinnerCount == 0}, + h("span", {className: "slds-assistive-text"}), + h("div", {className: "slds-spinner__dot-a"}), + h("div", {className: "slds-spinner__dot-b"}), + ), + h("a", {href: "#", id: "help-btn", title: "Import Help", onClick: this.onToggleHelpClick}, + h("div", {className: "icon"}) + ), + ), ), h("div", {className: "conf-section"}, h("div", {className: "conf-subsection"}, - h("div", {className: "conf-line"}, - h("label", {className: "conf-input", title: "With the tooling API you can query more metadata, but you cannot query regular data"}, - h("span", {className: "conf-label"}), - h("span", {className: "conf-value"}, - h("input", {type: "checkbox", checked: model.useToolingApi, onChange: this.onUseToolingApiChange, disabled: model.isWorking()}), - " Use Tooling API?" + h("div", {className: "area configure-import"}, + h("div", {className: "area-header"}, + h("h1", {}, "Configure Import") + ), + h("div", {className: "conf-line"}, + h("label", {className: "conf-input", title: "With the tooling API you can query more metadata, but you cannot query regular data"}, + h("span", {className: "conf-label"}, "Use Tooling API?"), + h("span", {className: "conf-value"}, + h("input", {type: "checkbox", checked: model.useToolingApi, onChange: this.onUseToolingApiChange, disabled: model.isWorking()}), + ) ) - ) - ), - h("div", {className: "conf-line"}, - h("label", {className: "conf-input"}, - h("span", {className: "conf-label"}, "Action"), - h("span", {className: "conf-value"}, - h("select", {value: model.importAction, onChange: this.onImportActionChange, disabled: model.isWorking()}, - h("option", {value: "create"}, "Insert"), - h("option", {value: "update"}, "Update"), - h("option", {value: "upsert"}, "Upsert"), - h("option", {value: "delete"}, "Delete") + ), + h("div", {className: "conf-line"}, + h("label", {className: "conf-input"}, + h("span", {className: "conf-label"}, "Action"), + h("span", {className: "conf-value"}, + h("select", {value: model.importAction, onChange: this.onImportActionChange, disabled: model.isWorking()}, + h("option", {value: "create"}, "Insert"), + h("option", {value: "update"}, "Update"), + h("option", {value: "upsert"}, "Upsert"), + h("option", {value: "delete"}, "Delete") + ) ) ) - ) - ), - h("div", {className: "conf-line"}, - h("label", {className: "conf-input"}, - h("span", {className: "conf-label"}, "Object"), - h("span", {className: "conf-value"}, - h("input", {type: "text", value: model.importType, onChange: this.onImportTypeChange, className: model.importTypeError() ? "confError" : "", disabled: model.isWorking(), list: "sobjectlist"}), - h("div", {className: "conf-error", hidden: !model.importTypeError()}, model.importTypeError()) + ), + h("div", {className: "conf-line"}, + h("label", {className: "conf-input"}, + h("span", {className: "conf-label"}, "Object"), + h("span", {className: "conf-value"}, + h("input", {type: "search", value: model.importType, onChange: this.onImportTypeChange, className: model.importTypeError() ? "object-list confError" : "object-list", disabled: model.isWorking(), list: "sobjectlist"}), + h("div", {className: "conf-error", hidden: !model.importTypeError()}, model.importTypeError()) + ) + ), + h("a", {className: "button field-info", href: model.showDescribeUrl(), target: "_blank", title: "Show field info for the selected object"}, + h("div", {className: "button-icon"}), ) ), - h("a", {className: "char-btn", href: model.showDescribeUrl(), title: "Show field info for the selected object"}, "i") - ), - h("div", {className: "conf-line"}, - h("span", {className: "conf-label"}, "Format"), - h("label", {}, h("input", {type: "radio", name: "data-input-format", value: "excel", checked: model.dataFormat == "excel", onChange: this.onDataFormatChange, disabled: model.isWorking()}), " ", h("span", {}, "Excel")), - " ", - h("label", {}, h("input", {type: "radio", name: "data-input-format", value: "csv", checked: model.dataFormat == "csv", onChange: this.onDataFormatChange, disabled: model.isWorking()}), " ", h("span", {}, "CSV")) - ), - h("div", {className: "conf-line"}, - h("label", {className: "conf-input"}, - h("span", {className: "conf-label"}, "Data"), - h("span", {className: "conf-value"}, - h("textarea", {id: "data", value: model.message(), onPaste: this.onDataPaste, className: model.dataError ? "confError" : "", disabled: model.isWorking(), readOnly: true, rows: 1}), - h("div", {className: "conf-error", hidden: !model.dataError}, model.dataError) + h("div", {className: "conf-line radio-buttons"}, + h("span", {className: "conf-label"}, "Format"), + h("label", {}, h("input", {type: "radio", name: "data-input-format", value: "excel", checked: model.dataFormat == "excel", onChange: this.onDataFormatChange, disabled: model.isWorking()}), " ", h("span", {}, "Excel")), + " ", + h("label", {}, h("input", {type: "radio", name: "data-input-format", value: "csv", checked: model.dataFormat == "csv", onChange: this.onDataFormatChange, disabled: model.isWorking()}), " ", h("span", {}, "CSV")) + ), + h("div", {className: "conf-line"}, + h("label", {className: "conf-input"}, + h("span", {className: "conf-label"}, "Data"), + h("span", {className: "conf-value"}, + h("textarea", {id: "data", value: model.message(), onPaste: this.onDataPaste, className: model.dataError ? "confError" : "", disabled: model.isWorking(), readOnly: true, rows: 1}), + h("div", {className: "conf-error", hidden: !model.dataError}, model.dataError) + ) ) - ) - ), - h("div", {className: "conf-line", hidden: model.importAction != "upsert"}, - h("label", {className: "conf-input", title: "Used in upserts to determine if an existing record should be updated or a new record should be created"}, - h("span", {className: "conf-label"}, "External ID:"), - h("span", {className: "conf-value"}, - h("input", {type: "text", value: model.externalId, onChange: this.onExternalIdChange, className: model.externalIdError() ? "confError" : "", disabled: model.isWorking(), list: "idlookuplist"}), - h("div", {className: "conf-error", hidden: !model.externalIdError()}, model.externalIdError()) + ), + h("div", {className: "conf-line", hidden: model.importAction != "upsert"}, + h("label", {className: "conf-input", title: "Used in upserts to determine if an existing record should be updated or a new record should be created"}, + h("span", {className: "conf-label"}, "External ID:"), + h("span", {className: "conf-value"}, + h("input", {type: "text", value: model.externalId, onChange: this.onExternalIdChange, className: model.externalIdError() ? "confError" : "", disabled: model.isWorking(), list: "idlookuplist"}), + h("div", {className: "conf-error", hidden: !model.externalIdError()}, model.externalIdError()) + ) ) - ) - ), - h("div", {className: "conf-line"}, - h("label", {className: "conf-input", title: "The number of records per batch. A higher value is faster but increases the risk of errors due to governor limits."}, - h("span", {className: "conf-label"}, "Batch size"), - h("span", {className: "conf-value"}, - h("input", {type: "number", value: model.batchSize, onChange: this.onBatchSizeChange, className: (model.batchSizeError() ? "confError" : "") + " batch-size"}), - h("div", {className: "conf-error", hidden: !model.batchSizeError()}, model.batchSizeError()) + ), + h("div", {className: "conf-line"}, + h("label", {className: "conf-input", title: "The number of records per batch. A higher value is faster but increases the risk of errors due to governor limits."}, + h("span", {className: "conf-label"}, "Batch size"), + h("span", {className: "conf-value"}, + h("input", {type: "number", value: model.batchSize, onChange: this.onBatchSizeChange, className: (model.batchSizeError() ? "confError" : "") + " batch-size"}), + h("div", {className: "conf-error", hidden: !model.batchSizeError()}, model.batchSizeError()) + ) ) - ) - ), - h("div", {className: "conf-line"}, - h("label", {className: "conf-input", title: "The number of batches to execute concurrently. A higher number is faster but increases the risk of errors due to lock congestion."}, - h("span", {className: "conf-label"}, "Threads"), - h("span", {className: "conf-value"}, - h("input", {type: "number", value: model.batchConcurrency, onChange: this.onBatchConcurrencyChange, className: (model.batchConcurrencyError() ? "confError" : "") + " batch-size"}), - h("span", {hidden: !model.isWorking()}, model.activeBatches), - h("div", {className: "conf-error", hidden: !model.batchConcurrencyError()}, model.batchConcurrencyError()) + ), + h("div", {className: "conf-line"}, + h("label", {className: "conf-input", title: "The number of batches to execute concurrently. A higher number is faster but increases the risk of errors due to lock congestion."}, + h("span", {className: "conf-label"}, "Threads"), + h("span", {className: "conf-value"}, + h("input", {type: "number", value: model.batchConcurrency, onChange: this.onBatchConcurrencyChange, className: (model.batchConcurrencyError() ? "confError" : "") + " batch-size"}), + h("span", {hidden: !model.isWorking()}, model.activeBatches), + h("div", {className: "conf-error", hidden: !model.batchConcurrencyError()}, model.batchConcurrencyError()) + ) ) - ) + ), + h("datalist", {id: "sobjectlist"}, model.sobjectList().map(data => h("option", {key: data, value: data}))), + h("datalist", {id: "idlookuplist"}, model.idLookupList().map(data => h("option", {key: data, value: data}))), + h("datalist", {id: "columnlist"}, model.columnList().map(data => h("option", {key: data, value: data}))) ), - h("datalist", {id: "sobjectlist"}, model.sobjectList().map(data => h("option", {key: data, value: data}))), - h("datalist", {id: "idlookuplist"}, model.idLookupList().map(data => h("option", {key: data, value: data}))), - h("datalist", {id: "columnlist"}, model.columnList().map(data => h("option", {key: data, value: data}))) ), h("div", {className: "conf-subsection columns-mapping"}, - h("div", {className: "columns-label"}, "Field mapping"), - h("div", {className: "conf-error confError", hidden: !model.importIdColumnError()}, model.importIdColumnError()), - h("div", {className: "conf-value"}, model.columns().map((column, index) => h(ColumnMapper, {key: index, model, column}))) + h("div", {className: "area"}, + h("div", {className: "area-header"}, + h("h1", {}, "Field Mapping") + ), + /* h("div", {className: "columns-label"}, "Field mapping"), */ + h("div", {className: "conf-error confError", hidden: !model.importIdColumnError()}, model.importIdColumnError()), + h("div", {className: "conf-value"}, model.columns().map((column, index) => h(ColumnMapper, {key: index, model, column}))) + ) ) ), - h("div", {className: "conf-line"}, - h("span", {className: "conf-label"}), - h("a", {href: "about:blank", id: "import-help-btn", onClick: this.onToggleHelpClick}, "Import help"), - h("button", {onClick: this.onDoImportClick, disabled: model.invalidInput() || model.isWorking() || model.importCounts().Queued == 0}, "Import"), - h("button", {disabled: !model.isWorking(), onClick: this.onToggleProcessingClick}, model.isWorking() && !model.isProcessingQueue ? "Resume queued" : "Cancel queued"), - h("button", {disabled: !model.importCounts().Failed > 0, onClick: this.onRetryFailedClick, className: "button-space"}, "Retry failed"), - h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsExcelClick, title: "Copy import result to clipboard for pasting into Excel or similar"}, "Copy (Excel format)"), - h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsCsvClick, className: "button-space", title: "Copy import result to clipboard for saving as a CSV file"}, "Copy (CSV)"), - h("button", {onClick: this.onCopyOptionsClick, title: "Save these import options by pasting them into Excel in the top left cell, just above the header row"}, "Copy options") - ), - h("div", {hidden: !model.showHelp}, - h("p", {}, "Use for quick one-off data imports."), - h("ul", {}, - h("li", {}, "Enter your CSV or Excel data in the box above.", - h("ul", {}, - h("li", {}, "The input must contain a header row with field API names."), - h("li", {}, "To use an external ID for a lookup field, the header row should contain the lookup relation name, the target sobject name and the external ID name separated by colons, e.g. \"MyLookupField__r:MyObject__c:MyExternalIdField__c\"."), - h("li", {}, "Empty cells insert null values."), - h("li", {}, "Number, date, time and checkbox values must conform to the relevant ", h("a", {href: "http://www.w3.org/TR/xmlschema-2/#built-in-primitive-datatypes", target: "_blank"}, "XSD datatypes"), "."), - h("li", {}, "Columns starting with an underscore are ignored."), - h("li", {}, "You can resume a previous import by including the \"__Status\" column in your input."), - h("li", {}, "You can supply the other import options by clicking \"Copy options\" and pasting the options into Excel in the top left cell, just above the header row.") - ) + h("div", {className: "area import-actions"}, + h("div", {className: "conf-line"}, + h("div", {className: "flex-wrapper"}, + h("button", {onClick: this.onDoImportClick, disabled: model.invalidInput() || model.isWorking() || model.importCounts().Queued == 0, className: "highlighted"}, "Run " + model.importActionName), + h("button", {disabled: !model.isWorking(), onClick: this.onToggleProcessingClick, className: model.isWorking() && !model.isProcessingQueue ? "": "cancel-btn"}, model.isWorking() && !model.isProcessingQueue ? "Resume Queued" : "Cancel Queued"), + h("button", {disabled: !model.importCounts().Failed > 0, onClick: this.onRetryFailedClick}, "Retry Failed"), + h("div", {className: "button-group"}, + h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsExcelClick, title: "Copy import result to clipboard for pasting into Excel or similar"}, "Copy (Excel format)"), + h("button", {disabled: !model.canCopy(), onClick: this.onCopyAsCsvClick, title: "Copy import result to clipboard for saving as a CSV file"}, "Copy (CSV)"), + ), + ), + h("div", {className: "status-group"}, + h("div", {}, + h(StatusBox, {model, name: "Queued"}), + h(StatusBox, {model, name: "Processing"}) + ), + h("div", {}, + h(StatusBox, {model, name: "Succeeded"}), + h(StatusBox, {model, name: "Failed"}) + ), + ), + h("div", {className: "flex-right"}, + h("button", {onClick: this.onCopyOptionsClick, title: "Save these import options by pasting them into Excel in the top left cell, just above the header row"}, "Copy Options") ), - h("li", {}, "Select your input format"), - h("li", {}, "Select an action (insert, update, upsert or delete)"), - h("li", {}, "Enter the API name of the object to import"), - h("li", {}, "Press Import") ), - h("p", {}, "Bulk API is not supported. Large data volumes may freeze or crash your browser.") - ), - h("div", {className: "status-group"}, - h("span", {className: "conf-label"}, "Status"), - h(StatusBox, {model, name: "Queued"}), - h(StatusBox, {model, name: "Processing"}), - h(StatusBox, {model, name: "Succeeded"}), - h(StatusBox, {model, name: "Failed"}) + h("div", {hidden: !model.showHelp, className: "help-text"}, + h("h3", {}, "Import Help"), + h("p", {}, "Use for quick one-off data imports."), + h("ul", {}, + h("li", {}, "Enter your CSV or Excel data in the box above.", + h("ul", {}, + h("li", {}, "The input must contain a header row with field API names."), + h("li", {}, "To use an external ID for a lookup field, the header row should contain the lookup relation name, the target sobject name and the external ID name separated by colons, e.g. \"MyLookupField__r:MyObject__c:MyExternalIdField__c\"."), + h("li", {}, "Empty cells insert null values."), + h("li", {}, "Number, date, time and checkbox values must conform to the relevant ", h("a", {href: "http://www.w3.org/TR/xmlschema-2/#built-in-primitive-datatypes", target: "_blank"}, "XSD datatypes"), "."), + h("li", {}, "Columns starting with an underscore are ignored."), + h("li", {}, "You can resume a previous import by including the \"__Status\" column in your input."), + h("li", {}, "You can supply the other import options by clicking \"Copy options\" and pasting the options into Excel in the top left cell, just above the header row.") + ) + ), + h("li", {}, "Select your input format"), + h("li", {}, "Select an action (insert, update, upsert or delete)"), + h("li", {}, "Enter the API name of the object to import"), + h("li", {}, "Press the Run button") + ), + h("p", {}, "Bulk API is not supported. Large data volumes may freeze or crash your browser.") + ), ), - h("div", {id: "result-table", ref: "scroller"}), - model.confirmPopup ? h("div", {}, - h("div", {id: "confirm-background"}, - h("div", {id: "confirm-dialog"}, - h("h1", {}, "Import"), - h("p", {}, "You are about to modify your data in Salesforce. This action cannot be undone."), - h("p", {}, model.confirmPopup.text), - h("div", {className: "dialog-buttons"}, - h("button", {onClick: this.onConfirmPopupYesClick}, "Import"), - h("button", {onClick: this.onConfirmPopupNoClick}, "Cancel") + h("div", {className: "area result-area"}, + h("div", {id: "result-table", ref: "scroller"}), + model.confirmPopup ? h("div", {}, + h("div", {id: "confirm-background"}, + h("div", {id: "confirm-dialog"}, + h("h1", {}, "Import"), + h("p", {}, "You are about to modify your data in Salesforce. This action cannot be undone."), + h("p", {}, model.confirmPopup.text), + h("div", {className: "dialog-buttons"}, + h("button", {onClick: this.onConfirmPopupYesClick}, model.importActionName), + h("button", {onClick: this.onConfirmPopupNoClick, className: "cancel-btn"}, "Cancel") + ) ) ) - ) - ) : null + ) : null + ) ); } } @@ -965,8 +1001,11 @@ class ColumnMapper extends React.Component { render() { let {model, column} = this.props; return h("div", {className: "conf-line"}, - h("input", {type: "text", list: "columnlist", value: column.columnValue, onChange: this.onColumnValueChange, className: column.columnError() ? "confError" : "", disabled: model.isWorking()}), - h("div", {className: "conf-error", hidden: !column.columnError()}, h("span", {}, column.columnError()), " ", h("a", {href: "about:blank", onClick: this.onColumnSkipClick, hidden: model.isWorking(), title: "Don't import this column"}, "Skip")) + h("label", {htmlFor: "col-" + column.columnIndex}, column.columnOriginalValue), + h("div", {className: "flex-wrapper"}, + h("input", {type: "search", list: "columnlist", value: column.columnValue, onChange: this.onColumnValueChange, className: column.columnError() ? "confError" : "", disabled: model.isWorking(), id: "col-" + column.columnIndex}), + h("div", {className: "conf-error", hidden: !column.columnError()}, h("span", {}, column.columnError()), " ", h("button", {onClick: this.onColumnSkipClick, hidden: model.isWorking(), title: "Don't import this column"}, "Skip")) + ) ); } } diff --git a/addon/data-load.css b/addon/data-load.css index dd29257..9b38257 100644 --- a/addon/data-load.css +++ b/addon/data-load.css @@ -1,3 +1,13 @@ +#result-table table { + border: none; +} +#result-table table tr td:first-child { + border-left: none; + padding-left: 8px; +} +#result-table table tr:hover>td { + background-color: #F3F2F2; +} .scrolltable-scrolled { position: relative; } @@ -6,24 +16,118 @@ position: absolute; } .scrolltable-cell { - border: 1px solid gray; + border: 1px solid #DDDBDA; overflow: hidden; - padding: 0 1px; + padding: 0 4px; background-color: white; white-space: pre; box-sizing: border-box; } .scrolltable-cell.header { - font-weight: bold; + font-weight: 700; + background-color: #FAFAF9; + border-top: none; + padding-top: 7px; + padding-bottom: 7px; + /* position: sticky; */ + /* top: 0; */ + /* z-index: 1; */ } -.pop-menu { +.scrolltable-cell a { + color: #006dcc; + text-decoration: underline; + text-decoration-style: dotted; +} +#result-table table { + border: none; +} +#result-table table tr td:first-child { + border-left: none; + padding-left: 8px; +} +#result-table table tr:hover>td { + background-color: #F3F2F2; +} +.scrolltable-scrolled { + position: relative; +} +.scrolltable-scrolled table { + border-collapse: collapse; position: absolute; - border: 1px solid gray; +} +.scrolltable-cell { + border: 1px solid #DDDBDA; + overflow: hidden; + padding: 0 4px; background-color: white; - padding: 5px 10px; - margin-left: 5px; - margin-top: -5px; + white-space: pre; + box-sizing: border-box; +} +.scrolltable-cell.header { + font-weight: 700; + background-color: #FAFAF9; + border-top: none; + padding-top: 7px; + padding-bottom: 7px; + /* position: sticky; */ + /* top: 0; */ + /* z-index: 1; */ +} +.scrolltable-cell a { + color: #006dcc; + text-decoration: underline; + text-decoration-style: dotted; +} +.pop-menu { + position: absolute; + padding: 7px 0; + background: rgb(255, 255, 255); + box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.16); + border: 1px solid rgb(221, 219, 218); + border-radius: 0.25rem; +} +.pop-menu.saveOptions { + right: 0; + top: 0; + margin: 124px 25px 0 0; + z-index: 2; } .pop-menu a { display: block; + text-decoration: none; + color: rgb(0, 112, 210); + padding: 5px 15px; + display: flex; + align-items: center; +} +.pop-menu a .icon { + /* background-color: #706E6B; */ + display: inline-block; + width: 1rem; + height: 1rem; + margin-right: 8px; +} +.pop-menu a.view-salesforce .icon { + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 1rem; + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/salesforce1.svg'); + -webkit-mask-position: center; +} +.pop-menu a.view-inspector .icon { + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 1rem; + -webkit-mask-image: url('chrome-extension://__MSG_@@extension_id__/images/salesforce-inspector-logo.svg'); + -webkit-mask-position: center; +} +.pop-menu a.view-salesforce .icon{ + background-color: #009DDC; +} +.pop-menu a.view-inspector .icon { + background-color: #D8BE5F; +} +.pop-menu a:hover { + background-color: #F3F2F2; } +.pop-menu a:active { + background-color: #ECEBEA; +} \ No newline at end of file diff --git a/addon/data-load.js b/addon/data-load.js index 03ee3f5..262f68d 100644 --- a/addon/data-load.js +++ b/addon/data-load.js @@ -175,15 +175,25 @@ function renderCell(rt, cell, td) { args.set("recordId", recordId); } aShow.href = "inspect.html?" + args; + aShow.target = "_blank"; aShow.textContent = "Show all data"; + aShow.className = "view-inspector"; + let aShowIcon = document.createElement("div"); + aShowIcon.className = "icon" pop.appendChild(aShow); + aShow.prepend(aShowIcon); } // If the recordId ends with 0000000000AAA it is a dummy ID such as the ID for the master record type 012000000000000AAA if (recordId && isRecordId(recordId) && !recordId.endsWith("0000000000AAA")) { let aView = document.createElement("a"); aView.href = "https://" + rt.sfHost + "/" + recordId; + aView.target = "_blank"; aView.textContent = "View in Salesforce"; + aView.className = "view-salesforce"; + let aviewIcon = document.createElement("div"); + aviewIcon.className = "icon"; pop.appendChild(aView); + aView.prepend(aviewIcon); } function closer(ev) { if (ev != e && ev.target.closest(".pop-menu") != pop) { diff --git a/addon/images/advanced_function.svg b/addon/images/advanced_function.svg new file mode 100644 index 0000000..51d0887 --- /dev/null +++ b/addon/images/advanced_function.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/addon/images/anchor.svg b/addon/images/anchor.svg new file mode 100644 index 0000000..85e50cc --- /dev/null +++ b/addon/images/anchor.svg @@ -0,0 +1,12 @@ + + + +Artboard +Created with Sketch. + + diff --git a/addon/images/boolean.svg b/addon/images/boolean.svg new file mode 100644 index 0000000..5783f42 --- /dev/null +++ b/addon/images/boolean.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/call.svg b/addon/images/call.svg new file mode 100644 index 0000000..772a840 --- /dev/null +++ b/addon/images/call.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/addon/images/checkin.svg b/addon/images/checkin.svg new file mode 100644 index 0000000..2dfaef4 --- /dev/null +++ b/addon/images/checkin.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/addon/images/clock.svg b/addon/images/clock.svg new file mode 100644 index 0000000..d5e71e5 --- /dev/null +++ b/addon/images/clock.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/addon/images/currency.svg b/addon/images/currency.svg new file mode 100644 index 0000000..03bd3be --- /dev/null +++ b/addon/images/currency.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/date-time.svg b/addon/images/date-time.svg new file mode 100644 index 0000000..aa21b9e --- /dev/null +++ b/addon/images/date-time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/date.svg b/addon/images/date.svg new file mode 100644 index 0000000..8570f53 --- /dev/null +++ b/addon/images/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/down.svg b/addon/images/down.svg new file mode 100644 index 0000000..32597d2 --- /dev/null +++ b/addon/images/down.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/addon/images/email.svg b/addon/images/email.svg new file mode 100644 index 0000000..b74a1e3 --- /dev/null +++ b/addon/images/email.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/addon/images/help.svg b/addon/images/help.svg new file mode 100644 index 0000000..1dca2b1 --- /dev/null +++ b/addon/images/help.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/addon/images/home.svg b/addon/images/home.svg new file mode 100644 index 0000000..d5302cd --- /dev/null +++ b/addon/images/home.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/addon/images/light_bulb.svg b/addon/images/light_bulb.svg new file mode 100644 index 0000000..28056fd --- /dev/null +++ b/addon/images/light_bulb.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/addon/images/lightning_blue_background.png b/addon/images/lightning_blue_background.png new file mode 100644 index 0000000..c26e1a6 Binary files /dev/null and b/addon/images/lightning_blue_background.png differ diff --git a/addon/images/link.svg b/addon/images/link.svg new file mode 100644 index 0000000..8652cd8 --- /dev/null +++ b/addon/images/link.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/addon/images/location.svg b/addon/images/location.svg new file mode 100644 index 0000000..533f68f --- /dev/null +++ b/addon/images/location.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/addon/images/lock.svg b/addon/images/lock.svg new file mode 100644 index 0000000..c6bdcf0 --- /dev/null +++ b/addon/images/lock.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/addon/images/multi-picklist.svg b/addon/images/multi-picklist.svg new file mode 100644 index 0000000..3ef3c52 --- /dev/null +++ b/addon/images/multi-picklist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/number.svg b/addon/images/number.svg new file mode 100644 index 0000000..ec3a516 --- /dev/null +++ b/addon/images/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/percent.svg b/addon/images/percent.svg new file mode 100644 index 0000000..91ddf22 --- /dev/null +++ b/addon/images/percent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/picklist.svg b/addon/images/picklist.svg new file mode 100644 index 0000000..ff51466 --- /dev/null +++ b/addon/images/picklist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/question_mark.svg b/addon/images/question_mark.svg new file mode 100644 index 0000000..1892b14 --- /dev/null +++ b/addon/images/question_mark.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/addon/images/quotation_marks.svg b/addon/images/quotation_marks.svg new file mode 100644 index 0000000..2ff0e25 --- /dev/null +++ b/addon/images/quotation_marks.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/addon/images/record_lookup.svg b/addon/images/record_lookup.svg new file mode 100644 index 0000000..1db2c31 --- /dev/null +++ b/addon/images/record_lookup.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/addon/images/relate.svg b/addon/images/relate.svg new file mode 100644 index 0000000..1534343 --- /dev/null +++ b/addon/images/relate.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/addon/images/salesforce-inspector-logo.svg b/addon/images/salesforce-inspector-logo.svg new file mode 100644 index 0000000..d68b0d7 --- /dev/null +++ b/addon/images/salesforce-inspector-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/addon/images/salesforce1.svg b/addon/images/salesforce1.svg new file mode 100644 index 0000000..43f552e --- /dev/null +++ b/addon/images/salesforce1.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/addon/images/search.svg b/addon/images/search.svg new file mode 100644 index 0000000..8ec1a78 --- /dev/null +++ b/addon/images/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/sobject.svg b/addon/images/sobject.svg new file mode 100644 index 0000000..bbe2152 --- /dev/null +++ b/addon/images/sobject.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/addon/images/steps.svg b/addon/images/steps.svg new file mode 100644 index 0000000..b65f860 --- /dev/null +++ b/addon/images/steps.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/addon/images/string.svg b/addon/images/string.svg new file mode 100644 index 0000000..1acfaf2 --- /dev/null +++ b/addon/images/string.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/images/textarea.svg b/addon/images/textarea.svg new file mode 100644 index 0000000..d471704 --- /dev/null +++ b/addon/images/textarea.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/addon/images/up.svg b/addon/images/up.svg new file mode 100644 index 0000000..815033f --- /dev/null +++ b/addon/images/up.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/addon/images/variable.svg b/addon/images/variable.svg new file mode 100644 index 0000000..d7e127c --- /dev/null +++ b/addon/images/variable.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/addon/slds-spinner.css b/addon/slds-spinner.css new file mode 100644 index 0000000..b9adde7 --- /dev/null +++ b/addon/slds-spinner.css @@ -0,0 +1,952 @@ +.slds-spinner_container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 9050; + background-color: rgba(255, 255, 255, .75); + visibility: visible; + opacity: 1; + -webkit-transition: opacity .2s ease, visibility 0s; + transition: opacity .2s ease, visibility 0s; + -webkit-transition-delay: 0s, .3s; + transition-delay: 0s, .3s +} + +.slds-spinner_container.is-hidden { + visibility: hidden; + opacity: 0; + -webkit-transition: opacity .2s ease, visibility 0s; + transition: opacity .2s ease, visibility 0s; + -webkit-transition-delay: 0s, 0s; + transition-delay: 0s, 0s +} + +.slds-spinner_container.hide { + display: none +} + +.slds-spinner { + position: absolute; + top: 50%; + /*! @noflip */ + left: 50%; + z-index: 9051; + -webkit-transform: translate(-50%, -50%) rotate(90deg); + transform: translate(-50%, -50%) rotate(90deg) +} + +.slds-spinner_inline { + position: relative; + top: auto; + left: auto; + right: auto; + bottom: auto; + -webkit-transform: none; + transform: none +} + +.slds-spinner, +.slds-spinner__dot-a, +.slds-spinner__dot-b { + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; + will-change: transform +} + +.slds-spinner__dot-a, +.slds-spinner__dot-b { + position: absolute; + top: 0; + left: 0; + width: 100% +} + +.slds-spinner:after, +.slds-spinner:before, +.slds-spinner__dot-a:after, +.slds-spinner__dot-a:before, +.slds-spinner__dot-b:after, +.slds-spinner__dot-b:before { + content: ""; + position: absolute; + background: #0070d2; + border-radius: 50%; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) +} + +.slds-spinner__dot-a { + -webkit-transform: rotate(60deg); + transform: rotate(60deg) +} + +.slds-spinner__dot-b { + -webkit-transform: rotate(120deg); + transform: rotate(120deg) +} + +.slds-spinner:before { + -webkit-animation-delay: -.083s; + animation-delay: -.083s +} + +.slds-spinner__dot-a:before { + -webkit-animation-delay: .083s; + animation-delay: .083s +} + +.slds-spinner__dot-b:before { + -webkit-animation-delay: 250ms; + animation-delay: 250ms +} + +.slds-spinner:after { + -webkit-animation-delay: .416s; + animation-delay: .416s +} + +.slds-spinner__dot-a:after { + -webkit-animation-delay: .583s; + animation-delay: .583s +} + +.slds-spinner__dot-b:after { + -webkit-animation-delay: 750ms; + animation-delay: 750ms +} + +.slds-spinner_delayed:before { + -webkit-animation-delay: .216s; + animation-delay: .216s +} + +.slds-spinner_delayed .slds-spinner__dot-a:before { + -webkit-animation-delay: .383s; + animation-delay: .383s +} + +.slds-spinner_delayed .slds-spinner__dot-b:before { + -webkit-animation-delay: 550ms; + animation-delay: 550ms +} + +.slds-spinner_delayed:after { + -webkit-animation-delay: .716s; + animation-delay: .716s +} + +.slds-spinner_delayed .slds-spinner__dot-a:after { + -webkit-animation-delay: .883s; + animation-delay: .883s +} + +.slds-spinner_delayed .slds-spinner__dot-b:after { + -webkit-animation-delay: 1.05s; + animation-delay: 1.05s +} + +.slds-spinner--brand .slds-spinner__dot-a:after, +.slds-spinner--brand .slds-spinner__dot-a:before, +.slds-spinner--brand .slds-spinner__dot-b:after, +.slds-spinner--brand .slds-spinner__dot-b:before, +.slds-spinner--brand.slds-spinner:after, +.slds-spinner--brand.slds-spinner:before, +.slds-spinner_brand .slds-spinner__dot-a:after, +.slds-spinner_brand .slds-spinner__dot-a:before, +.slds-spinner_brand .slds-spinner__dot-b:after, +.slds-spinner_brand .slds-spinner__dot-b:before, +.slds-spinner_brand.slds-spinner:after, +.slds-spinner_brand.slds-spinner:before { + background-color: #1589ee +} + +.slds-spinner--inverse .slds-spinner__dot-a:after, +.slds-spinner--inverse .slds-spinner__dot-a:before, +.slds-spinner--inverse .slds-spinner__dot-b:after, +.slds-spinner--inverse .slds-spinner__dot-b:before, +.slds-spinner--inverse.slds-spinner:after, +.slds-spinner--inverse.slds-spinner:before, +.slds-spinner_inverse .slds-spinner__dot-a:after, +.slds-spinner_inverse .slds-spinner__dot-a:before, +.slds-spinner_inverse .slds-spinner__dot-b:after, +.slds-spinner_inverse .slds-spinner__dot-b:before, +.slds-spinner_inverse.slds-spinner:after, +.slds-spinner_inverse.slds-spinner:before { + background-color: #fff +} + +.slds-spinner--xx-small, +.slds-spinner_xx-small { + width: .5rem +} + +.slds-spinner--xx-small.slds-spinner_inline, +.slds-spinner_xx-small.slds-spinner_inline { + height: .5rem +} + +.slds-spinner--xx-small .slds-spinner__dot-a:after, +.slds-spinner--xx-small .slds-spinner__dot-a:before, +.slds-spinner--xx-small .slds-spinner__dot-b:after, +.slds-spinner--xx-small .slds-spinner__dot-b:before, +.slds-spinner--xx-small.slds-spinner:after, +.slds-spinner--xx-small.slds-spinner:before, +.slds-spinner_xx-small .slds-spinner__dot-a:after, +.slds-spinner_xx-small .slds-spinner__dot-a:before, +.slds-spinner_xx-small .slds-spinner__dot-b:after, +.slds-spinner_xx-small .slds-spinner__dot-b:before, +.slds-spinner_xx-small.slds-spinner:after, +.slds-spinner_xx-small.slds-spinner:before { + width: .125rem; + height: .125rem +} + +.slds-spinner--xx-small .slds-spinner__dot-a:before, +.slds-spinner--xx-small .slds-spinner__dot-b:before, +.slds-spinner--xx-small.slds-spinner:before, +.slds-spinner_xx-small .slds-spinner__dot-a:before, +.slds-spinner_xx-small .slds-spinner__dot-b:before, +.slds-spinner_xx-small.slds-spinner:before { + top: -.0625rem; + left: -.0625rem; + -webkit-animation-name: dotsBounceBefore-extraExtraSmall; + animation-name: dotsBounceBefore-extraExtraSmall +} + +.slds-spinner--xx-small .slds-spinner__dot-a:after, +.slds-spinner--xx-small .slds-spinner__dot-b:after, +.slds-spinner--xx-small.slds-spinner:after, +.slds-spinner_xx-small .slds-spinner__dot-a:after, +.slds-spinner_xx-small .slds-spinner__dot-b:after, +.slds-spinner_xx-small.slds-spinner:after { + top: -.0625rem; + right: -.0625rem; + -webkit-animation-name: dotsBounceAfter-extraExtraSmall; + animation-name: dotsBounceAfter-extraExtraSmall +} + +@-webkit-keyframes dotsBounceBefore-extraExtraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.275, .0425, .34, .265); + animation-timing-function: cubic-bezier(.275, .0425, .34, .265) + } + + 80% { + -webkit-transform: translate3d(-.125rem, 0, 0); + transform: translate3d(-.125rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, .555, .35, .715); + animation-timing-function: cubic-bezier(0, .555, .35, .715) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes dotsBounceBefore-extraExtraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.275, .0425, .34, .265); + animation-timing-function: cubic-bezier(.275, .0425, .34, .265) + } + + 80% { + -webkit-transform: translate3d(-.125rem, 0, 0); + transform: translate3d(-.125rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, .555, .35, .715); + animation-timing-function: cubic-bezier(0, .555, .35, .715) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@-webkit-keyframes dotsBounceAfter-extraExtraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-animation-timing-function: cubic-bezier(.275, .0425, .34, .265); + animation-timing-function: cubic-bezier(.275, .0425, .34, .265); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 80% { + -webkit-animation-timing-function: cubic-bezier(0, .555, .35, .715); + animation-timing-function: cubic-bezier(0, .555, .35, .715); + -webkit-transform: translate3d(.125rem, 0, 0); + transform: translate3d(.125rem, 0, 0) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes dotsBounceAfter-extraExtraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-animation-timing-function: cubic-bezier(.275, .0425, .34, .265); + animation-timing-function: cubic-bezier(.275, .0425, .34, .265); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 80% { + -webkit-animation-timing-function: cubic-bezier(0, .555, .35, .715); + animation-timing-function: cubic-bezier(0, .555, .35, .715); + -webkit-transform: translate3d(.125rem, 0, 0); + transform: translate3d(.125rem, 0, 0) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +.slds-spinner--x-small, +.slds-spinner_x-small { + width: 1rem +} + +.slds-spinner--x-small.slds-spinner_inline, +.slds-spinner_x-small.slds-spinner_inline { + height: 1rem +} + +.slds-spinner--x-small .slds-spinner__dot-a:after, +.slds-spinner--x-small .slds-spinner__dot-a:before, +.slds-spinner--x-small .slds-spinner__dot-b:after, +.slds-spinner--x-small .slds-spinner__dot-b:before, +.slds-spinner--x-small.slds-spinner:after, +.slds-spinner--x-small.slds-spinner:before, +.slds-spinner_x-small .slds-spinner__dot-a:after, +.slds-spinner_x-small .slds-spinner__dot-a:before, +.slds-spinner_x-small .slds-spinner__dot-b:after, +.slds-spinner_x-small .slds-spinner__dot-b:before, +.slds-spinner_x-small.slds-spinner:after, +.slds-spinner_x-small.slds-spinner:before { + width: .25rem; + height: .25rem +} + +.slds-spinner--x-small .slds-spinner__dot-a:before, +.slds-spinner--x-small .slds-spinner__dot-b:before, +.slds-spinner--x-small.slds-spinner:before, +.slds-spinner_x-small .slds-spinner__dot-a:before, +.slds-spinner_x-small .slds-spinner__dot-b:before, +.slds-spinner_x-small.slds-spinner:before { + top: -.125rem; + left: -.125rem; + -webkit-animation-name: dotsBounceBefore-extraSmall; + animation-name: dotsBounceBefore-extraSmall +} + +.slds-spinner--x-small .slds-spinner__dot-a:after, +.slds-spinner--x-small .slds-spinner__dot-b:after, +.slds-spinner--x-small.slds-spinner:after, +.slds-spinner_x-small .slds-spinner__dot-a:after, +.slds-spinner_x-small .slds-spinner__dot-b:after, +.slds-spinner_x-small.slds-spinner:after { + top: -.125rem; + right: -.125rem; + -webkit-animation-name: dotsBounceAfter-extraSmall; + animation-name: dotsBounceAfter-extraSmall +} + +@-webkit-keyframes dotsBounceBefore-extraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.25rem, 0, 0); + transform: translate3d(-.25rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes dotsBounceBefore-extraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.25rem, 0, 0); + transform: translate3d(-.25rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@-webkit-keyframes dotsBounceAfter-extraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.25rem, 0, 0); + transform: translate3d(.25rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes dotsBounceAfter-extraSmall { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.25rem, 0, 0); + transform: translate3d(.25rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +.slds-spinner--small, +.slds-spinner_small { + width: 1.25rem +} + +.slds-spinner--small.slds-spinner_inline, +.slds-spinner_small.slds-spinner_inline { + height: 1.25rem +} + +.slds-spinner--small .slds-spinner__dot-a:after, +.slds-spinner--small .slds-spinner__dot-a:before, +.slds-spinner--small .slds-spinner__dot-b:after, +.slds-spinner--small .slds-spinner__dot-b:before, +.slds-spinner--small.slds-spinner:after, +.slds-spinner--small.slds-spinner:before, +.slds-spinner_small .slds-spinner__dot-a:after, +.slds-spinner_small .slds-spinner__dot-a:before, +.slds-spinner_small .slds-spinner__dot-b:after, +.slds-spinner_small .slds-spinner__dot-b:before, +.slds-spinner_small.slds-spinner:after, +.slds-spinner_small.slds-spinner:before { + width: .25rem; + height: .25rem +} + +.slds-spinner--small .slds-spinner__dot-a:before, +.slds-spinner--small .slds-spinner__dot-b:before, +.slds-spinner--small.slds-spinner:before, +.slds-spinner_small .slds-spinner__dot-a:before, +.slds-spinner_small .slds-spinner__dot-b:before, +.slds-spinner_small.slds-spinner:before { + top: -.125rem; + left: -.125rem; + -webkit-animation-name: dotsBounceBefore-small; + animation-name: dotsBounceBefore-small +} + +.slds-spinner--small .slds-spinner__dot-a:after, +.slds-spinner--small .slds-spinner__dot-b:after, +.slds-spinner--small.slds-spinner:after, +.slds-spinner_small .slds-spinner__dot-a:after, +.slds-spinner_small .slds-spinner__dot-b:after, +.slds-spinner_small.slds-spinner:after { + top: -.125rem; + right: -.125rem; + -webkit-animation-name: dotsBounceAfter-small; + animation-name: dotsBounceAfter-small +} + +@-webkit-keyframes dotsBounceBefore-small { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.375rem, 0, 0); + transform: translate3d(-.375rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes dotsBounceBefore-small { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.375rem, 0, 0); + transform: translate3d(-.375rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@-webkit-keyframes dotsBounceAfter-small { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.375rem, 0, 0); + transform: translate3d(.375rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +@keyframes dotsBounceAfter-small { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.375rem, 0, 0); + transform: translate3d(.375rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +.slds-spinner--medium, +.slds-spinner_medium { + width: 2rem +} + +.slds-spinner--medium.slds-spinner_inline, +.slds-spinner_medium.slds-spinner_inline { + height: 2rem +} + +.slds-spinner--medium .slds-spinner__dot-a:after, +.slds-spinner--medium .slds-spinner__dot-a:before, +.slds-spinner--medium .slds-spinner__dot-b:after, +.slds-spinner--medium .slds-spinner__dot-b:before, +.slds-spinner--medium.slds-spinner:after, +.slds-spinner--medium.slds-spinner:before, +.slds-spinner_medium .slds-spinner__dot-a:after, +.slds-spinner_medium .slds-spinner__dot-a:before, +.slds-spinner_medium .slds-spinner__dot-b:after, +.slds-spinner_medium .slds-spinner__dot-b:before, +.slds-spinner_medium.slds-spinner:after, +.slds-spinner_medium.slds-spinner:before { + width: .5rem; + height: .5rem +} + +.slds-spinner--medium .slds-spinner__dot-a:before, +.slds-spinner--medium .slds-spinner__dot-b:before, +.slds-spinner--medium.slds-spinner:before, +.slds-spinner_medium .slds-spinner__dot-a:before, +.slds-spinner_medium .slds-spinner__dot-b:before, +.slds-spinner_medium.slds-spinner:before { + -webkit-animation-name: dotsBounceBefore-medium; + animation-name: dotsBounceBefore-medium; + top: -.25rem; + left: -.25rem; +} + +.slds-spinner--medium .slds-spinner__dot-a:after, +.slds-spinner--medium .slds-spinner__dot-b:after, +.slds-spinner--medium.slds-spinner:after, +.slds-spinner_medium .slds-spinner__dot-a:after, +.slds-spinner_medium .slds-spinner__dot-b:after, +.slds-spinner_medium.slds-spinner:after { + -webkit-animation-name: dotsBounceAfter-medium; + animation-name: dotsBounceAfter-medium; + top: -.25rem; + right: -.25rem +} + +@-webkit-keyframes dotsBounceBefore-medium { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.5rem, 0, 0); + transform: translate3d(-.5rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes dotsBounceBefore-medium { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.5rem, 0, 0); + transform: translate3d(-.5rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@-webkit-keyframes dotsBounceAfter-medium { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.5rem, 0, 0); + transform: translate3d(.5rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +@keyframes dotsBounceAfter-medium { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.5rem, 0, 0); + transform: translate3d(.5rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +.slds-spinner--large, +.slds-spinner_large { + width: 2.75rem +} + +.slds-spinner--large.slds-spinner_inline, +.slds-spinner_large.slds-spinner_inline { + height: 2.75rem +} + +.slds-spinner--large .slds-spinner__dot-a:after, +.slds-spinner--large .slds-spinner__dot-a:before, +.slds-spinner--large .slds-spinner__dot-b:after, +.slds-spinner--large .slds-spinner__dot-b:before, +.slds-spinner--large.slds-spinner:after, +.slds-spinner--large.slds-spinner:before, +.slds-spinner_large .slds-spinner__dot-a:after, +.slds-spinner_large .slds-spinner__dot-a:before, +.slds-spinner_large .slds-spinner__dot-b:after, +.slds-spinner_large .slds-spinner__dot-b:before, +.slds-spinner_large.slds-spinner:after, +.slds-spinner_large.slds-spinner:before { + width: .625rem; + height: .625rem +} + +.slds-spinner--large .slds-spinner__dot-a:before, +.slds-spinner--large .slds-spinner__dot-b:before, +.slds-spinner--large.slds-spinner:before, +.slds-spinner_large .slds-spinner__dot-a:before, +.slds-spinner_large .slds-spinner__dot-b:before, +.slds-spinner_large.slds-spinner:before { + -webkit-animation-name: dotsBounceBefore-medium; + animation-name: dotsBounceBefore-medium; + top: -.3125rem; + left: -.3125rem +} + +.slds-spinner--large .slds-spinner__dot-a:after, +.slds-spinner--large .slds-spinner__dot-b:after, +.slds-spinner--large.slds-spinner:after, +.slds-spinner_large .slds-spinner__dot-a:after, +.slds-spinner_large .slds-spinner__dot-b:after, +.slds-spinner_large.slds-spinner:after { + -webkit-animation-name: dotsBounceAfter-medium; + animation-name: dotsBounceAfter-medium; + top: -.3125rem; + right: -.3125rem +} + +@-webkit-keyframes dotsBounceBefore-large { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.75rem, 0, 0); + transform: translate3d(-.75rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +@keyframes dotsBounceBefore-large { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(-.75rem, 0, 0); + transform: translate3d(-.75rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +@-webkit-keyframes dotsBounceAfter-large { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.75rem, 0, 0); + transform: translate3d(.75rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} + +@keyframes dotsBounceAfter-large { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } + + 60% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-animation-timing-function: cubic-bezier(.55, .085, .68, .53); + animation-timing-function: cubic-bezier(.55, .085, .68, .53) + } + + 80% { + -webkit-transform: translate3d(.75rem, 0, 0); + transform: translate3d(.75rem, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43); + animation-timing-function: cubic-bezier(0, 1.11, .7, 1.43) + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } +} \ No newline at end of file