Macros CRUD apis and Execution
This commit is contained in:
parent
c5d875cb08
commit
a55e9eef86
14 changed files with 631 additions and 95 deletions
|
@ -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();
|
||||
|
|
|
@ -34,6 +34,7 @@ const settings = accountId => ({
|
|||
'automation_list',
|
||||
'macros_wrapper',
|
||||
'macros_new',
|
||||
'macros_edit',
|
||||
],
|
||||
menuItems: [
|
||||
{
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||
"LOADING": "Fetching macros",
|
||||
"SIDEBAR_TXT": "<p><b>Macros</b> <p>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.</p> <p>Please refer to the <a href='https://chatwoot.com/docs/macros'>docs</a> to read more about macros in detail.</p>",
|
||||
"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."
|
||||
},
|
||||
"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"
|
||||
"PERSONAL": {
|
||||
"LABEL": "Personal",
|
||||
"DESCRIPTION": "This macro will be personal to you and not be available to others."
|
||||
}
|
||||
}
|
||||
},
|
||||
"EXECUTE": {
|
||||
"BUTTON_TOOLTIP": "Execute",
|
||||
"PREVIEW": "Preview Macro",
|
||||
"EXECUTED_SUCCESFULLY": "Macro executed successfully"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -93,6 +93,16 @@
|
|||
/>
|
||||
</accordion-item>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'macros'">
|
||||
<accordion-item
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.MACROS')"
|
||||
:is-open="isContactSidebarItemOpen('is_macro_open')"
|
||||
compact
|
||||
@click="value => toggleSidebarUIState('is_macro_open', value)"
|
||||
>
|
||||
<macros-list :conversation-id="conversationId" />
|
||||
</accordion-item>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
|
@ -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: {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<div>
|
||||
<p
|
||||
v-if="!uiFlags.isFetching && !macros.length"
|
||||
class="no-items-error-message"
|
||||
>
|
||||
{{ $t('MACROS.LIST.404') }}
|
||||
</p>
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching"
|
||||
:message="$t('MACROS.LOADING')"
|
||||
/>
|
||||
<div v-if="!uiFlags.isFetching && macros.length" class="macros-list">
|
||||
<macro-item
|
||||
v-for="macro in macros"
|
||||
:key="macro.id"
|
||||
:macro="macro"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import MacroItem from './MacroItem.vue';
|
||||
export default {
|
||||
components: {
|
||||
MacroItem,
|
||||
},
|
||||
props: {
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
macros: ['macros/getMacros'],
|
||||
uiFlags: 'macros/getUIFlags',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
if (!this.macros.length) {
|
||||
this.$store.dispatch('macros/get');
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.macros-list {
|
||||
padding: var(--space-smaller);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="macro">
|
||||
<span class="text-truncate">{{ macro.name }}</span>
|
||||
<div class="macros-actions">
|
||||
<woot-button
|
||||
v-tooltip.top-start="$t('MACROS.EXECUTE.PREVIEW')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="info"
|
||||
class="mr-2"
|
||||
@click="toggleMacroPreview(macro)"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top-start="$t('MACROS.EXECUTE.BUTTON_TOOLTIP')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="play-circle"
|
||||
:is-loading="isExecuting"
|
||||
@click="executeMacro(macro)"
|
||||
/>
|
||||
</div>
|
||||
<transition name="menu-slide">
|
||||
<macro-preview
|
||||
v-if="showPreview"
|
||||
v-on-clickaway="closeMacroPreview"
|
||||
:macro="macro"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import MacroPreview from './MacroPreview.vue';
|
||||
export default {
|
||||
components: {
|
||||
MacroPreview,
|
||||
},
|
||||
mixins: [alertMixin, clickaway],
|
||||
props: {
|
||||
macro: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExecuting: false,
|
||||
showPreview: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async executeMacro(macro) {
|
||||
try {
|
||||
this.isExecuting = true;
|
||||
await this.$store.dispatch('macros/execute', {
|
||||
macroId: macro.id,
|
||||
conversationIds: [this.conversationId],
|
||||
});
|
||||
this.showAlert(this.$t('MACROS.EXECUTE.EXECUTED_SUCCESFULLY'));
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('MACROS.ERROR'));
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
}
|
||||
},
|
||||
toggleMacroPreview() {
|
||||
this.showPreview = !this.showPreview;
|
||||
},
|
||||
closeMacroPreview() {
|
||||
this.showPreview = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.macro {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
padding: var(--space-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--border-radius-normal);
|
||||
color: var(--s-700);
|
||||
|
||||
&:hover {
|
||||
background: var(--s-25);
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--w-300);
|
||||
}
|
||||
|
||||
.macros-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
.mr-2 {
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="macro-preview">
|
||||
<p class="macro-title">{{ macro.name }}</p>
|
||||
<div v-for="(action, i) in resolvedMacro" :key="i" class="macro-block">
|
||||
<div v-if="i !== macro.actions.length - 1" class="macro-block-border" />
|
||||
<div class="macro-block-dot" />
|
||||
<p class="macro-action-name">{{ action.actionName }}</p>
|
||||
<p class="macro-action-params">{{ action.actionValue }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AUTOMATION_ACTION_TYPES } from 'dashboard/routes/dashboard/settings/automation/constants.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
macro: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
resolvedMacro() {
|
||||
return this.macro.actions.map(action => {
|
||||
return {
|
||||
actionName: this.getActionName(action.action_name),
|
||||
actionValue: this.getActionValue(
|
||||
action.action_name,
|
||||
action.action_params
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
valueStore(type) {
|
||||
switch (type) {
|
||||
case 'assign_team':
|
||||
case 'send_email_to_team':
|
||||
return this.$store.getters['teams/getTeams'];
|
||||
case 'add_label':
|
||||
return this.$store.getters['labels/getLabels'].map(i => {
|
||||
return {
|
||||
id: i.title,
|
||||
name: i.title,
|
||||
};
|
||||
});
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
getActionName(key) {
|
||||
return AUTOMATION_ACTION_TYPES.find(i => i.key === key).label;
|
||||
},
|
||||
resolveTeamIds(ids) {
|
||||
const allTeams = this.$store.getters['teams/getTeams'];
|
||||
return ids
|
||||
.map(id => {
|
||||
const team = allTeams.find(i => i.id === id);
|
||||
return team ? team.name : '';
|
||||
})
|
||||
.join(', ');
|
||||
},
|
||||
resolveLabels(ids) {
|
||||
const allLabels = this.$store.getters['labels/getLabels'];
|
||||
return ids
|
||||
.map(id => {
|
||||
const label = allLabels.find(i => i.title === id);
|
||||
return label ? label.title : '';
|
||||
})
|
||||
.join(', ');
|
||||
},
|
||||
resolveSendEmailToTeam(obj) {
|
||||
return ` ${obj.message} -
|
||||
${this.resolveTeamIds(obj.team_ids)}`;
|
||||
},
|
||||
getActionValue(key, params) {
|
||||
switch (key) {
|
||||
case 'assign_team':
|
||||
return this.resolveTeamIds(params);
|
||||
case 'add_label':
|
||||
return this.resolveLabels(params);
|
||||
case 'send_email_to_team':
|
||||
return this.resolveSendEmailToTeam(params[0]);
|
||||
case 'send_email_transcript':
|
||||
return this.resolveSendEmailToTeam(params[0]);
|
||||
case 'mute_conversation':
|
||||
case 'snooze_conversation':
|
||||
case 'resolve_conversation':
|
||||
return null;
|
||||
case 'send_webhook_event':
|
||||
case 'send_message':
|
||||
return params[0];
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.macro-preview {
|
||||
position: absolute;
|
||||
max-height: calc(var(--space-giga) * 1.5);
|
||||
min-height: var(--space-jumbo);
|
||||
width: var(--space-giga);
|
||||
border-radius: var(--border-radius-normal);
|
||||
background-color: var(--white);
|
||||
box-shadow: var(--shadow-dropdown-pane);
|
||||
bottom: var(--space-three);
|
||||
right: var(--space-jumbo);
|
||||
overflow-y: auto;
|
||||
padding: var(--space-slab);
|
||||
|
||||
.macro-title {
|
||||
margin-bottom: var(--space-slab);
|
||||
}
|
||||
|
||||
.macro-block {
|
||||
position: relative;
|
||||
padding-left: var(--space-slab);
|
||||
padding-bottom: var(--space-slab);
|
||||
|
||||
.macro-block-border {
|
||||
top: 0.625rem;
|
||||
position: absolute;
|
||||
bottom: -0.5rem;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
background-color: var(--s-100);
|
||||
}
|
||||
|
||||
.macro-block-dot {
|
||||
position: absolute;
|
||||
left: -0.35rem;
|
||||
height: var(--space-small);
|
||||
width: var(--space-small);
|
||||
border: 2px solid var(--s-100);
|
||||
background-color: var(--white);
|
||||
border-radius: var(--border-radius-full);
|
||||
top: 0.4375rem;
|
||||
}
|
||||
}
|
||||
|
||||
.macro-action-name {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--s-500);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<div class="column content-box">
|
||||
<router-link to="macros/new" class="button success button--fixed-right-top">
|
||||
<router-link
|
||||
:to="addAccountScoping('settings/macros/new')"
|
||||
class="button success button--fixed-right-top"
|
||||
>
|
||||
<fluent-icon icon="add-circle" />
|
||||
<span class="button__content">
|
||||
{{ $t('MACROS.HEADER_BTN_TXT') }}
|
||||
|
@ -18,7 +21,7 @@
|
|||
v-if="uiFlags.isFetching"
|
||||
:message="$t('MACROS.LOADING')"
|
||||
/>
|
||||
<table class="woot-table">
|
||||
<table v-if="!uiFlags.isFetching && records.length" class="woot-table">
|
||||
<thead>
|
||||
<th
|
||||
v-for="thHeader in $t('MACROS.LIST.TABLE_HEADER')"
|
||||
|
@ -44,6 +47,9 @@
|
|||
</td>
|
||||
<td class="macro-visibility">{{ macro.visibility }}</td>
|
||||
<td class="button-wrapper">
|
||||
<router-link
|
||||
:to="addAccountScoping(`settings/macros/${macro.id}/edit`)"
|
||||
>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.FORM.EDIT')"
|
||||
variant="smooth"
|
||||
|
@ -52,6 +58,7 @@
|
|||
class-names="grey-btn"
|
||||
icon="edit"
|
||||
/>
|
||||
</router-link>
|
||||
<woot-button
|
||||
v-tooltip.top="$t('MACROS.FORM.DELETE')"
|
||||
variant="smooth"
|
||||
|
@ -59,6 +66,7 @@
|
|||
size="tiny"
|
||||
icon="dismiss-circle"
|
||||
class-names="grey-btn"
|
||||
@click="openDeletePopup(macro, index)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -69,26 +77,82 @@
|
|||
<span v-dompurify-html="$t('MACROS.SIDEBAR_TXT')" />
|
||||
</div>
|
||||
</div>
|
||||
<woot-delete-modal
|
||||
:show.sync="showDeleteConfirmationPopup"
|
||||
:on-close="closeDeletePopup"
|
||||
:on-confirm="confirmDeletion"
|
||||
:title="$t('LABEL_MGMT.DELETE.CONFIRM.TITLE')"
|
||||
:message="$t('MACROS.DELETE.CONFIRM.MESSAGE')"
|
||||
:message-value="deleteMessage"
|
||||
:confirm-text="deleteConfirmText"
|
||||
:reject-text="deleteRejectText"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
import accountMixin from 'dashboard/mixins/account.js';
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
},
|
||||
mixins: [alertMixin, accountMixin],
|
||||
data() {
|
||||
return {
|
||||
showDeleteConfirmationPopup: false,
|
||||
selectedResponse: {},
|
||||
loading: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
records: ['macros/getMacros'],
|
||||
uiFlags: 'macros/getUIFlags',
|
||||
}),
|
||||
// Delete Modal
|
||||
deleteConfirmText() {
|
||||
return `${this.$t('MACROS.DELETE.CONFIRM.YES')} ${
|
||||
this.selectedResponse.name
|
||||
}`;
|
||||
},
|
||||
deleteRejectText() {
|
||||
return `${this.$t('MACROS.DELETE.CONFIRM.NO')} ${
|
||||
this.selectedResponse.name
|
||||
}`;
|
||||
},
|
||||
deleteMessage() {
|
||||
return ` ${this.selectedResponse.name}?`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('macros/get');
|
||||
},
|
||||
methods: {
|
||||
openDeletePopup(response) {
|
||||
this.showDeleteConfirmationPopup = true;
|
||||
this.selectedResponse = response;
|
||||
},
|
||||
closeDeletePopup() {
|
||||
this.showDeleteConfirmationPopup = false;
|
||||
},
|
||||
confirmDeletion() {
|
||||
this.loading[this.selectedResponse.id] = true;
|
||||
this.closeDeletePopup();
|
||||
this.deleteMacro(this.selectedResponse.id);
|
||||
},
|
||||
async deleteMacro(id) {
|
||||
try {
|
||||
await this.$store.dispatch('macros/delete', id);
|
||||
this.showAlert(this.$t('MACROS.DELETE.API.SUCCESS_MESSAGE'));
|
||||
this.loading[this.selectedResponse.id] = false;
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('MACROS.DELETE.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,11 +2,18 @@
|
|||
<div class="column content-box">
|
||||
<div class="row">
|
||||
<div class="small-8 columns with-right-space macros-canvas">
|
||||
<ul class="macros-feed-container">
|
||||
<p v-if="!uiFlags.isFetching && !macro" class="no-items-error-message">
|
||||
{{ $t('MACROS.LIST.404') }}
|
||||
</p>
|
||||
<woot-loading-state
|
||||
v-if="uiFlags.isFetching"
|
||||
:message="$t('MACROS.LOADING')"
|
||||
/>
|
||||
<ul v-if="!uiFlags.isFetching && macro" class="macros-feed-container">
|
||||
<!-- start flow pill -->
|
||||
<li class="macros-feed-item">
|
||||
<div class="macros-item macros-pill">
|
||||
<span>Start Flow</span>
|
||||
<span>{{ $t('MACROS.EDITOR.START_FLOW') }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<!-- start flow pill -->
|
||||
|
@ -77,7 +84,7 @@
|
|||
<!-- end flow pill -->
|
||||
<li class="macros-feed-item">
|
||||
<div class="macros-item macros-pill">
|
||||
<span>End Flow</span>
|
||||
<span>{{ $t('MACROS.EDITOR.END_FLOW') }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<!-- end flow pill -->
|
||||
|
@ -88,14 +95,16 @@
|
|||
<div>
|
||||
<woot-input
|
||||
v-model.trim="macro.name"
|
||||
label="Macro name"
|
||||
placeholder="Enter a name for your macro"
|
||||
error="Please enter a name"
|
||||
:label="$t('MACROS.ADD.FORM.NAME.LABEL')"
|
||||
:placeholder="$t('MACROS.ADD.FORM.NAME.PLACEHOLDER')"
|
||||
:error="
|
||||
$v.macro.name.$error ? $t('MACROS.ADD.FORM.NAME.ERROR') : null
|
||||
"
|
||||
:class="{ error: $v.macro.name.$error }"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">Macro Visibility</p>
|
||||
<p class="title">{{ $t('MACROS.EDITOR.VISIBILITY.LABEL') }}</p>
|
||||
<div class="macros-form-radio-group">
|
||||
<button
|
||||
class="card"
|
||||
|
@ -108,36 +117,36 @@
|
|||
type="solid"
|
||||
class="visibility-check"
|
||||
/>
|
||||
<p class="title">Global</p>
|
||||
<p class="title">
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.GLOBAL.LABEL') }}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
This macro is available globally for all agents in this
|
||||
account.
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.GLOBAL.DESCRIPTION') }}
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
class="card"
|
||||
:class="{ active: macro.visibility === 'private' }"
|
||||
@click="macro.visibility = 'private'"
|
||||
:class="{ active: macro.visibility === 'personal' }"
|
||||
@click="macro.visibility = 'personal'"
|
||||
>
|
||||
<fluent-icon
|
||||
v-if="macro.visibility === 'private'"
|
||||
v-if="macro.visibility === 'personal'"
|
||||
icon="checkmark-circle"
|
||||
type="solid"
|
||||
class="visibility-check"
|
||||
/>
|
||||
<p class="title">Private</p>
|
||||
<p class="title">
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.PERSONAL.LABEL') }}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
This macro will be private to you and not be available to
|
||||
others.
|
||||
{{ $t('MACROS.EDITOR.VISIBILITY.PERSONAL.DESCRIPTION') }}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="macros-information-panel">
|
||||
<fluent-icon icon="info" size="20" />
|
||||
<p>
|
||||
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') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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;
|
|
@ -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'],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue