diff --git a/app/javascript/dashboard/api/macros.js b/app/javascript/dashboard/api/macros.js index b275dec3a..d515b7b52 100644 --- a/app/javascript/dashboard/api/macros.js +++ b/app/javascript/dashboard/api/macros.js @@ -1,9 +1,20 @@ +/* global axios */ import ApiClient from './ApiClient'; class MacrosAPI extends ApiClient { constructor() { super('macros', { accountScoped: true }); } + + getSingleMacro(macroId) { + return axios.get(`${this.url}/${macroId}`); + } + + executeMacro({ macroId, conversationIds }) { + return axios.post(`${this.url}/${macroId}/execute`, { + conversation_ids: conversationIds, + }); + } } export default new MacrosAPI(); diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js index a2a57b218..d4f304c73 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js @@ -34,6 +34,7 @@ const settings = accountId => ({ 'automation_list', 'macros_wrapper', 'macros_new', + 'macros_edit', ], menuItems: [ { diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 39857520f..6182c6f01 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -207,7 +207,8 @@ "CONVERSATION_LABELS": "Conversation Labels", "CONVERSATION_INFO": "Conversation Information", "CONTACT_ATTRIBUTES": "Contact Attributes", - "PREVIOUS_CONVERSATION": "Previous Conversations" + "PREVIOUS_CONVERSATION": "Previous Conversations", + "MACROS": "Macros" } }, "CONVERSATION_CUSTOM_ATTRIBUTES": { diff --git a/app/javascript/dashboard/i18n/locale/en/macros.json b/app/javascript/dashboard/i18n/locale/en/macros.json index 359b912d7..f6ee277e3 100644 --- a/app/javascript/dashboard/i18n/locale/en/macros.json +++ b/app/javascript/dashboard/i18n/locale/en/macros.json @@ -5,6 +5,8 @@ "HEADER_BTN_TXT_SAVE": "Save macro", "LOADING": "Fetching macros", "SIDEBAR_TXT": "

Macros

Macros help you run a series of actions and tasks. They're a list of instructions which tell chatwoot how to execute based on the order which you define them.

Please refer to the docs to read more about macros in detail.

", + "ERROR": "Something went wrong. Please try again", + "ORDER_INFO": "Macros will run in the order you add yout actions. You can rearrange them by dragging them by the handle beside each action.", "ADD": { "TITLE": "Add new Macro", "SUBMIT": "Create", @@ -12,8 +14,8 @@ "FORM": { "NAME": { "LABEL": "Macro name", - "PLACEHOLDER": "Enter macro name", - "ERROR": "Name is required" + "PLACEHOLDER": "Enter a name for your macro", + "ERROR": "Please enter a macro name" }, "DESC": { "LABEL": "Description", @@ -30,7 +32,7 @@ "ACTION_BUTTON_LABEL": "Add Action", "API": { "SUCCESS_MESSAGE": "Macro added successfully", - "ERROR_MESSAGE": "Could not able to create macro, Please try again later" + "ERROR_MESSAGE": "Unable to create macro, Please try again later" } }, "LIST": { @@ -66,13 +68,6 @@ "ERROR_MESSAGE": "Could not update Macro, Please try again later" } }, - "CLONE": { - "TOOLTIP": "Clone", - "API": { - "SUCCESS_MESSAGE": "Macro cloned successfully", - "ERROR_MESSAGE": "Could not clone Macro, Please try again later" - } - }, "FORM": { "EDIT": "Edit", "CREATE": "Create", @@ -80,32 +75,30 @@ "CANCEL": "Cancel", "RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below" }, - "CONDITION": { - "DELETE_MESSAGE": "You need to have atleast one condition to save" - }, "ACTION": { "DELETE_MESSAGE": "You need to have atleast one action to save", "TEAM_MESSAGE_INPUT_PLACEHOLDER": "Enter your message here", "TEAM_DROPDOWN_PLACEHOLDER": "Select teams" }, - "TOGGLE": { - "ACTIVATION_TITLE": "Activate Macro", - "DEACTIVATION_TITLE": "Deactivate Macro", - "ACTIVATION_DESCRIPTION": "This action will activate the Macro '{macroName}'. Are you sure you want to proceed?", - "DEACTIVATION_DESCRIPTION": "This action will deactivate the Macro '{macroName}'. Are you sure you want to proceed?", - "ACTIVATION_SUCCESFUL": "Macro Activated Successfully", - "DEACTIVATION_SUCCESFUL": "Macro Deactivated Successfully", - "ACTIVATION_ERROR": "Could not Activate Macro, Please try again later", - "DEACTIVATION_ERROR": "Could not Deactivate Macro, Please try again later", - "CONFIRMATION_LABEL": "Yes", - "CANCEL_LABEL": "No" + "EDITOR": { + "START_FLOW": "Start Flow", + "END_FLOW": "End Flow", + "VISIBILITY": { + "LABEL": "Macro Visibility", + "GLOBAL": { + "LABEL": "Global", + "DESCRIPTION": "This macro is available globally for all agents in this account." + }, + "PERSONAL": { + "LABEL": "Personal", + "DESCRIPTION": "This macro will be personal to you and not be available to others." + } + } }, - "ATTACHMENT": { - "UPLOAD_ERROR": "Could not upload attachment, Please try again", - "LABEL_IDLE": "Upload Attachment", - "LABEL_UPLOADING": "Uploading...", - "LABEL_UPLOADED": "Succesfully Uploaded", - "LABEL_UPLOAD_FAILED": "Upload Failed" + "EXECUTE": { + "BUTTON_TOOLTIP": "Execute", + "PREVIEW": "Preview Macro", + "EXECUTED_SUCCESFULLY": "Macro executed successfully" } } } diff --git a/app/javascript/dashboard/mixins/uiSettings.js b/app/javascript/dashboard/mixins/uiSettings.js index e265975e1..4e129c130 100644 --- a/app/javascript/dashboard/mixins/uiSettings.js +++ b/app/javascript/dashboard/mixins/uiSettings.js @@ -4,6 +4,7 @@ export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = [ { name: 'conversation_info' }, { name: 'contact_attributes' }, { name: 'previous_conversation' }, + { name: 'macros' }, ]; export const DEFAULT_CONTACT_SIDEBAR_ITEMS_ORDER = [ { name: 'contact_attributes' }, @@ -16,8 +17,8 @@ export default { uiSettings: 'getUISettings', }), conversationSidebarItemsOrder() { - const { conversation_sidebar_items_order: itemsOrder } = this.uiSettings; - return itemsOrder || DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER; + // const { conversation_sidebar_items_order: itemsOrder } = this.uiSettings; + return DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER; }, contactSidebarItemsOrder() { const { contact_sidebar_items_order: itemsOrder } = this.uiSettings; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue index 4405ed2ad..34171b9a3 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue @@ -93,6 +93,16 @@ /> +
+ + + +
@@ -105,6 +115,7 @@ import alertMixin from 'shared/mixins/alertMixin'; import AccordionItem from 'dashboard/components/Accordion/AccordionItem'; import ContactConversations from './ContactConversations.vue'; import ConversationAction from './ConversationAction.vue'; +import MacrosList from './Macros/List.vue'; import ContactInfo from './contact/ContactInfo'; import ConversationInfo from './ConversationInfo'; @@ -123,6 +134,7 @@ export default { CustomAttributeSelector, ConversationAction, draggable, + MacrosList, }, mixins: [alertMixin, uiSettingsMixin], props: { diff --git a/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue b/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue new file mode 100644 index 000000000..77197af56 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue @@ -0,0 +1,53 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue b/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue new file mode 100644 index 000000000..efb679b63 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroPreview.vue b/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroPreview.vue new file mode 100644 index 000000000..eb9e0c167 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroPreview.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue index be021ae3b..1f4bfcdce 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue @@ -1,6 +1,9 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/New.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue similarity index 68% rename from app/javascript/dashboard/routes/dashboard/settings/macros/New.vue rename to app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue index c6a707713..8fefd25d9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/New.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue @@ -2,11 +2,18 @@
-
    +

    + {{ $t('MACROS.LIST.404') }} +

    + +
    • - Start Flow + {{ $t('MACROS.EDITOR.START_FLOW') }}
    • @@ -77,7 +84,7 @@
    • - End Flow + {{ $t('MACROS.EDITOR.END_FLOW') }}
    • @@ -88,14 +95,16 @@
      -

      Macro Visibility

      +

      {{ $t('MACROS.EDITOR.VISIBILITY.LABEL') }}

      - Macros will run in the order you add yout actions. You can - rearrange them by dragging them by the handle beside each - action. + {{ $t('MACROS.ORDER_INFO') }}

      @@ -163,6 +172,8 @@ import { required, requiredIf } from 'vuelidate/lib/validators'; import draggable from 'vuedraggable'; import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js'; import alertMixin from 'shared/mixins/alertMixin'; +import { mapGetters } from 'vuex'; + export default { components: { ActionInput, @@ -196,33 +207,48 @@ export default { }, data() { return { + mode: 'CREATE', dragEnabled: true, dragging: false, - macroActionTypes: AUTOMATION_ACTION_TYPES, + loadingMacro: false, macroVisibilityOptions: [ { id: 'global', - title: 'Global', + title: this.$t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL'), checked: true, }, { - id: 'private', - title: 'Private', + id: 'personal', + title: this.$t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL'), checked: false, }, ], - macro: { - name: '', - actions: [ - { - action_name: 'assign_team', - action_params: [], - }, - ], - visibility: 'global', - }, + macro: {}, }; }, + computed: { + ...mapGetters({ + uiFlags: 'macros/getUIFlags', + }), + macroActionTypes() { + // Because we do not support attachments in macros - yet! + return AUTOMATION_ACTION_TYPES.filter( + obj => obj.key !== 'send_attachment' + ); + }, + macroId() { + return this.$route.params.macroId; + }, + }, + watch: { + '$route.params.macroId': { + handler() { + this.$v.$reset(); + this.fetchMacro(); + }, + immediate: true, + }, + }, methods: { appendNewAction() { this.macro.actions.push({ @@ -271,13 +297,84 @@ export default { this.$v.$touch(); if (this.$v.$invalid) return; try { - const macro = JSON.parse(JSON.stringify(this.macro)); + const action = this.mode === 'EDIT' ? 'macros/update' : 'macros/create'; + const successMessage = + this.mode === 'EDIT' + ? this.$t('MACROS.EDIT.API.SUCCESS_MESSAGE') + : this.$t('MACROS.ADD.API.SUCCESS_MESSAGE'); + let macro = JSON.parse(JSON.stringify(this.macro)); macro.actions = actionQueryGenerator(macro.actions); - await this.$store.dispatch('macros/create', macro); - this.showAlert('Macro created Succesfully'); + await this.$store.dispatch(action, macro); + this.showAlert(this.$t(successMessage)); this.$router.push({ name: 'macros_wrapper' }); } catch (error) { - this.showAlert('Something went wrong, please try again'); + this.showAlert(this.$t('MACROS.ERROR')); + } + }, + formatMacro(macro) { + const formattedActions = macro.actions.map(action => { + let actionParams = []; + if (action.action_params.length) { + const inputType = this.macroActionTypes.find( + item => item.key === action.action_name + ).inputType; + if (inputType === 'multi_select') { + actionParams = [ + ...this.getActionDropdownValues(action.action_name), + ].filter(item => [...action.action_params].includes(item.id)); + } else if (inputType === 'team_message') { + actionParams = { + team_ids: [ + ...this.getActionDropdownValues(action.action_name), + ].filter(item => + [...action.action_params[0].team_ids].includes(item.id) + ), + message: action.action_params[0].message, + }; + } else actionParams = [...action.action_params]; + } + return { + ...action, + action_params: actionParams, + }; + }); + return { + ...macro, + actions: formattedActions, + }; + }, + async manifestMacro() { + const macroAvailable = this.$store.getters['macros/getMacro']( + this.macroId + ); + if (macroAvailable) this.macro = this.formatMacro(macroAvailable); + else { + const rawMacro = await this.$store.dispatch( + 'macros/getSingleMacro', + this.macroId + ); + this.macro = this.formatMacro(rawMacro); + } + }, + fetchMacro() { + if (this.macroId) { + this.mode = 'EDIT'; + this.$store.dispatch('agents/get'); + this.$store.dispatch('teams/get'); + this.$store.dispatch('labels/get'); + this.manifestMacro(); + } else { + this.mode = 'CREATE'; + this.macro = { + name: '', + actions: [ + { + action_name: 'assign_team', + action_params: [], + }, + ], + visibility: 'global', + }; } }, }, @@ -290,7 +387,7 @@ export default { } .macros-canvas { background-image: radial-gradient(#e0e0e0bd 1.2px, transparent 0); - background-size: 16px 16px; + background-size: var(--space-normal) var(--space-normal); height: 100%; max-height: 100%; padding: var(--space-normal) var(--space-three); @@ -300,6 +397,7 @@ export default { .macros-properties-panel { padding: var(--space-slab); background-color: var(--white); + // full screen height subtracted by the height of the header height: calc(100vh - 5.6rem); display: flex; flex-direction: column; @@ -308,6 +406,7 @@ export default { .macros-feed-container { list-style-type: none; + // Make sure the macros ui is not too wide in large screens max-width: 800px; .macros-feed-item:not(:last-child) { @@ -319,9 +418,9 @@ export default { .macros-feed-draggable:after { content: ''; position: absolute; - height: 30px; + height: var(--space-three); width: 3px; - margin-left: 24px; + margin-left: var(--space-medium); background-image: url("data:image/svg+xml,%3Csvg width='3' height='31' viewBox='0 0 3 31' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='1.50098' y1='0.579529' x2='1.50098' y2='30.5795' stroke='%2393afc8' stroke-width='2' stroke-dasharray='5 5'/%3E%3C/svg%3E%0A"); } .macros-feed-draggable { @@ -349,7 +448,7 @@ export default { font-size: var(--font-size-default); border-radius: var(--border-radius-rounded); position: relative; - margin-left: 10px; + margin-left: var(--space-one); } .macros-action-item_button { height: var(--space-three); @@ -361,7 +460,7 @@ export default { font-size: var(--font-size-default); border-radius: var(--border-radius-rounded); position: relative; - margin-left: 10px; + margin-left: var(--space-one); &.add { background-color: var(--g-100); @@ -389,6 +488,7 @@ export default { background-color: var(--white); padding: var(--space-slab); border-radius: var(--border-radius-medium); + // Custom shadow so it wont look to heavy over the dots. box-shadow: rgb(0 0 0 / 3%) 0px 6px 24px 0px, rgb(0 0 0 / 6%) 0px 0px 0px 1px; &:hover { @@ -398,7 +498,7 @@ export default { } } } - + //Adding a custom shake animation on error because the save button and the action inputs were too far to notice that there was an error. &.has-error { animation: shake 0.3s ease-in-out 0s 2; background-color: var(--r-50); @@ -412,7 +512,7 @@ export default { .macros-form-radio-group { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 1rem; + gap: var(--space-slab); .card { padding: var(--space-small); @@ -434,7 +534,7 @@ export default { .visibility-check { position: absolute; - color: var(--w-300); + color: var(--w-500); top: var(--space-small); right: var(--space-small); } @@ -444,10 +544,10 @@ export default { .title { display: block; margin: 0; - font-size: 1.4rem; - font-weight: 500; + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); line-height: 1.8; - color: #3c4858; + color: var(--color-body); } .content-box { padding: 0; diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js index 0f5ddd413..a4309fdd4 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js @@ -1,6 +1,6 @@ import SettingsContent from '../Wrapper'; import Macros from './Index'; -const NewMacro = () => import('./New.vue'); +const MacroEditor = () => import('./MacroEditor.vue'); import { frontendURL } from '../../../../helper/URLHelper'; export default { @@ -23,7 +23,13 @@ export default { { path: 'new', name: 'macros_new', - component: NewMacro, + component: MacroEditor, + roles: ['administrator'], + }, + { + path: ':macroId/edit', + name: 'macros_edit', + component: MacroEditor, roles: ['administrator'], }, ], diff --git a/app/javascript/dashboard/store/modules/macros.js b/app/javascript/dashboard/store/modules/macros.js index a165f8d87..11572e45f 100644 --- a/app/javascript/dashboard/store/modules/macros.js +++ b/app/javascript/dashboard/store/modules/macros.js @@ -9,6 +9,7 @@ export const state = { isCreating: false, isDeleting: false, isUpdating: false, + isExecuting: false, }, }; @@ -16,6 +17,9 @@ export const getters = { getMacros(_state) { return _state.records; }, + getMacro: $state => id => { + return $state.records.find(record => record.id === Number(id)); + }, getUIFlags(_state) { return _state.uiFlags; }, @@ -33,17 +37,40 @@ export const actions = { commit(types.SET_MACROS_UI_FLAG, { isFetching: false }); } }, + // eslint-disable-next-line consistent-return + getSingleMacro: async function getMacroById({ commit }, macroId) { + commit(types.SET_MACROS_UI_FLAG, { isFetching: true }); + try { + const response = await MacrosAPI.getSingleMacro(macroId); + return response.data.payload; + } catch (error) { + // Ignore error + } finally { + commit(types.SET_MACROS_UI_FLAG, { isFetching: false }); + } + }, create: async function createMacro({ commit }, macrosObj) { commit(types.SET_MACROS_UI_FLAG, { isCreating: true }); try { const response = await MacrosAPI.create(macrosObj); - commit(types.ADD_MACRO, response.data); + commit(types.ADD_MACRO, response.data.payload); } catch (error) { throw new Error(error); } finally { commit(types.SET_MACROS_UI_FLAG, { isCreating: false }); } }, + execute: async function executeMacro({ commit }, macrosObj) { + commit(types.SET_MACROS_UI_FLAG, { isExecuting: true }); + try { + const response = await MacrosAPI.executeMacro(macrosObj); + return response.data.payload; + } catch (error) { + throw new Error(error); + } finally { + commit(types.SET_MACROS_UI_FLAG, { isExecuting: false }); + } + }, update: async ({ commit }, { id, ...updateObj }) => { commit(types.SET_MACROS_UI_FLAG, { isUpdating: true }); try { diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index a942efc2c..8e6e4712d 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -117,6 +117,7 @@ "person-add-outline": "M17.5 12a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11zm-5.477 2a6.47 6.47 0 0 0-.709 1.5H4.253a.749.749 0 0 0-.75.75v.577c0 .535.192 1.053.54 1.46c1.253 1.469 3.22 2.214 5.957 2.214c.597 0 1.157-.035 1.68-.106c.246.495.553.954.912 1.367c-.795.16-1.66.24-2.592.24c-3.146 0-5.532-.906-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.578A2.249 2.249 0 0 1 4.253 14h7.77zm5.477 0l-.09.008a.5.5 0 0 0-.402.402L17 14.5V17h-2.496l-.09.008a.5.5 0 0 0-.402.402l-.008.09l.008.09a.5.5 0 0 0 .402.402l.09.008H17L17 20.5l.008.09a.5.5 0 0 0 .402.402l.09.008l.09-.008a.5.5 0 0 0 .402-.402L18 20.5V18h2.504l.09-.008a.5.5 0 0 0 .402-.402l.008-.09l-.008-.09a.5.5 0 0 0-.402-.402l-.09-.008H18L18 14.5l-.008-.09a.5.5 0 0 0-.402-.402L17.5 14zM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7z", "person-assign-outline": "M11.313 15.5a6.471 6.471 0 0 1 .709-1.5h-7.77a2.249 2.249 0 0 0-2.249 2.25v.577c0 .892.319 1.756.899 2.435c1.566 1.834 3.952 2.74 7.098 2.74c.931 0 1.796-.08 2.592-.24a6.51 6.51 0 0 1-.913-1.366c-.524.07-1.083.105-1.68.105c-2.737 0-4.703-.745-5.957-2.213a2.25 2.25 0 0 1-.539-1.461v-.578a.75.75 0 0 1 .75-.749h7.06ZM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7ZM23 17.5a5.5 5.5 0 1 1-11 0a5.5 5.5 0 0 1 11 0Zm-4.647-2.853a.5.5 0 0 0-.707.707L19.293 17H15a.5.5 0 1 0 0 1h4.293l-1.647 1.647a.5.5 0 0 0 .707.707l2.5-2.5a.497.497 0 0 0 .147-.345V17.5a.498.498 0 0 0-.15-.357l-2.497-2.496Z", "person-outline": "M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.575c0 .894-.32 1.76-.902 2.438-1.57 1.834-3.957 2.739-7.102 2.739-3.146 0-5.532-.905-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.577a2.249 2.249 0 0 1 2.249-2.25h11.501Zm0 1.5H6.253a.749.749 0 0 0-.75.749v.577c0 .536.192 1.054.54 1.461 1.253 1.468 3.219 2.214 5.957 2.214s4.706-.746 5.962-2.214a2.25 2.25 0 0 0 .541-1.463v-.575a.749.749 0 0 0-.749-.75ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z", + "play-circle-outline": "M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12Zm8.856-3.845A1.25 1.25 0 0 0 9 9.248v5.504a1.25 1.25 0 0 0 1.856 1.093l5.757-3.189a.75.75 0 0 0 0-1.312l-5.757-3.189Z", "power-outline": "M8.204 4.82a.75.75 0 0 1 .634 1.36A7.51 7.51 0 0 0 4.5 12.991c0 4.148 3.358 7.51 7.499 7.51s7.499-3.362 7.499-7.51a7.51 7.51 0 0 0-4.323-6.804.75.75 0 1 1 .637-1.358 9.01 9.01 0 0 1 5.186 8.162c0 4.976-4.029 9.01-9 9.01C7.029 22 3 17.966 3 12.99a9.01 9.01 0 0 1 5.204-8.17ZM12 2.496a.75.75 0 0 1 .743.648l.007.102v7.5a.75.75 0 0 1-1.493.102l-.007-.102v-7.5a.75.75 0 0 1 .75-.75Z", "quote-outline": "M7.5 6a2.5 2.5 0 0 1 2.495 2.336l.005.206c-.01 3.555-1.24 6.614-3.705 9.223a.75.75 0 1 1-1.09-1.03c1.64-1.737 2.66-3.674 3.077-5.859A2.5 2.5 0 1 1 7.5 6Zm9 0a2.5 2.5 0 0 1 2.495 2.336l.005.206c-.01 3.56-1.238 6.614-3.705 9.223a.75.75 0 1 1-1.09-1.03c1.643-1.738 2.662-3.672 3.078-5.859A2.5 2.5 0 1 1 16.5 6Zm-9 1.5a1 1 0 1 0 .993 1.117l.007-.124a1 1 0 0 0-1-.993Zm9 0a1 1 0 1 0 .993 1.117l.007-.124a1 1 0 0 0-1-.993Z", "resize-large-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v1.5a.75.75 0 0 1-1.5 0v-1.5A3.25 3.25 0 0 1 6.25 3h1.5a.75.75 0 0 1 0 1.5h-1.5ZM19.5 6.25a1.75 1.75 0 0 0-1.75-1.75h-1.5a.75.75 0 0 1 0-1.5h1.5A3.25 3.25 0 0 1 21 6.25v1.5a.75.75 0 0 1-1.5 0v-1.5ZM19.5 17.75a1.75 1.75 0 0 1-1.75 1.75h-1.5a.75.75 0 0 0 0 1.5h1.5A3.25 3.25 0 0 0 21 17.75v-1.5a.75.75 0 0 0-1.5 0v1.5ZM4.5 17.75c0 .966.784 1.75 1.75 1.75h1.5a.75.75 0 0 1 0 1.5h-1.5A3.25 3.25 0 0 1 3 17.75v-1.5a.75.75 0 0 1 1.5 0v1.5ZM8.25 6A2.25 2.25 0 0 0 6 8.25v7.5A2.25 2.25 0 0 0 8.25 18h7.5A2.25 2.25 0 0 0 18 15.75v-7.5A2.25 2.25 0 0 0 15.75 6h-7.5ZM7.5 8.25a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 .75.75v7.5a.75.75 0 0 1-.75.75h-7.5a.75.75 0 0 1-.75-.75v-7.5Z",