Browse Source

Added bag game component.

John Doe 1 year ago
parent
commit
6f5c20b68a

+ 1 - 0
public/arrow-down.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-down"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>

+ 1 - 0
public/arrow-up.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>

BIN
public/bag.png


BIN
public/bag_empty.png


+ 1 - 0
public/cursor-move.svg

@@ -0,0 +1 @@
+<svg version="1.1" viewBox="0 0 36 36" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" role="img" width="20" height="20" fill="#ffffff"><path d="M28.85,12.89a1,1,0,0,0-1.42,0,1,1,0,0,0,0,1.41L30.14,17H19V5.86l2.69,2.7a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.42L18,2,12.89,7.15a1,1,0,0,0-.29.71,1,1,0,0,0,1.71.7L17,5.86V17H5.86l2.7-2.69a1,1,0,0,0,0-1.41,1,1,0,0,0-1.42,0L2,18l5.14,5.11a1,1,0,0,0,.71.29,1,1,0,0,0,.7-1.71L5.86,19H17V30.14l-2.69-2.7a1,1,0,0,0-1.71.7,1,1,0,0,0,.29.71L18,34l5.11-5.14a1,1,0,0,0,0-1.42,1,1,0,0,0-1.41,0L19,30.14V19H30.14l-2.7,2.69a1,1,0,0,0,.7,1.71,1,1,0,0,0,.71-.29L34,18Z" class="clr-i-outline clr-i-outline-path-1"/></svg>

+ 1 - 0
public/edit.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>

+ 1 - 0
public/eye-with-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20" id="entypo-eye-with-line" width="32" height="32" fill="#888886"><g><path d="M18.521 1.478a1 1 0 0 0-1.414 0L1.48 17.107a1 1 0 1 0 1.414 1.414L18.52 2.892a1 1 0 0 0 0-1.414zM3.108 13.498l2.56-2.56A4.18 4.18 0 0 1 5.555 10c0-2.379 1.99-4.309 4.445-4.309.286 0 .564.032.835.082l1.203-1.202A12.645 12.645 0 0 0 10 4.401C3.44 4.4 0 9.231 0 10c0 .423 1.057 2.09 3.108 3.497zm13.787-6.993l-2.562 2.56c.069.302.111.613.111.935 0 2.379-1.989 4.307-4.444 4.307-.284 0-.56-.032-.829-.081l-1.204 1.203c.642.104 1.316.17 2.033.17 6.56 0 10-4.833 10-5.599 0-.424-1.056-2.09-3.105-3.495z"/></g></svg>

+ 1 - 0
public/github.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>

+ 1 - 0
public/share-2.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-share-2"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>

+ 1 - 0
public/squared-plus.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20" id="entypo-squared-plus" width="128" height="128" fill="#f9fbfa"><g><path d="M16 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4H9v-4H5V9h4V5h2v4h4v2z"/></g></svg>

+ 1 - 0
public/trash.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>

+ 1 - 0
public/x.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>

+ 185 - 0
src/gameComponents/Bag.jsx

@@ -0,0 +1,185 @@
+import React, { memo } from "react";
+import styled, { css } from "styled-components";
+import { useItemActions, useItemInteraction, useWire } from "react-sync-board";
+import { opacify } from "color2k";
+
+import { media2Url } from "../mediaLibrary";
+import { isItemInsideElement, getItemElement } from "../utils";
+
+const BagWrapper = styled.div`
+${({
+    width,
+    height,
+    labelBackgroundColor = "#cccccc33",
+}) => css`
+    width: ${width}px;
+    height: ${height}px;
+    position: relative;
+    & .bag__label {
+      font-size: 1.5em;
+      user-select: none;
+      background-color: ${opacify(labelBackgroundColor, 1)};
+      position: absolute;
+      border-radius: 0.5em;
+      color: var(--color-darkGrey);
+    }
+
+    & .bag__label.left {
+      padding: 1em 0em;
+      top: 1em;
+      left: -1em;
+      letter-spacing: -3px;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+    }
+
+    & .bag__label.top {
+      padding: 0em 1em;
+      top: -1em;
+      left: 1em;
+    }
+  `}
+`;
+
+const BagImage = styled.img`
+  transition: opacity 300ms;
+  pointer-events: none;
+  display: ${({ visible }) => (visible ? 'block' : 'none')};
+`;
+
+const ItemCounter = styled.div`
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: ${({ counterSize }) => `${counterSize}px`}
+`;
+
+const Bag = ({
+    families,
+    storedItems = [],
+    label,
+    width,
+    height,
+    bagImage = "/bag.png",
+    emptyBagImage = "/bag_empty.png",
+    labelPosition = "left",
+    countersize = 50,
+    countervisible = true,
+    labelBackgroundColor = "#cccccc33",
+    id,
+    setState,
+}) => {
+    const { isMaster } = useWire("board");
+    const { register } = useItemInteraction("place");
+    const {
+        pushItem,
+        getItems,
+        batchUpdateItems,
+        removeItems,
+    } = useItemActions();
+
+    const bagRef = React.useRef(null);
+
+    const noFamilies = !Array.isArray(families) || families.length === 0;
+
+    const imageContent = media2Url(bagImage) || "/bag.png";
+    const emptyImageContent = media2Url(emptyBagImage) || "/bag_empty.png";
+    const size = {};
+
+    if (width) {
+        size.width = width;
+    }
+    if (height) {
+        size.height = height;
+    }
+
+    const onInsideItem = React.useCallback(
+        async (itemIds) => {
+            const items = await getItems(itemIds);
+            const insideItems = items
+                .filter((item) => noFamilies || families.includes(item.groupId))
+                .filter(({ id: itemId }) =>
+                    // Skip the container itself.
+                    itemId !== id &&
+                    isItemInsideElement(getItemElement(itemId), bagRef.current)
+                )
+                .map(({ id }) => id);
+
+            if (!insideItems.length) return;
+
+            const newItems = await getItems(insideItems);
+            await removeItems(newItems.map(item => item.id));
+
+            storedItems = newItems.concat(storedItems);
+
+            setState((prevState) => ({
+                ...prevState,
+                storedItems: storedItems,
+            }));
+        }, [families, storedItems, id, setState, getItems, removeItems]
+    );
+
+    const onExtract = React.useCallback(
+        async () => {
+            const [bag] = await getItems([id]);
+
+            storedItems = [...storedItems];
+            const item = storedItems.shift();
+
+            if (item) {
+                setState((prevState) => ({
+                    ...prevState,
+                    storedItems: storedItems,
+                }));
+
+                await pushItem({
+                    ...item,
+                    x: bag.x + bagRef.current.clientWidth / 3.0,
+                    y: bag.y + bagRef.current.clientHeight / 3.0,
+                });
+            }
+        }, [id, bagRef, storedItems, setState, pushItem]
+    );
+
+    const extract = (e) => {
+        e.stopPropagation();
+        onExtract();
+    }
+
+    React.useEffect(() => {
+        const unregisterList = [];
+        unregisterList.push(register(onInsideItem));
+
+        return () => {
+            unregisterList.forEach((callback) => callback());
+        };
+    }, [onInsideItem, register]);
+
+    return (
+        <BagWrapper onDoubleClick={extract} ref={bagRef}
+            labelBackgroundColor={labelBackgroundColor}
+            width={width}
+            height={height}
+        >
+            {countervisible && (<ItemCounter counterSize={countersize} >{storedItems.length}</ItemCounter>)}
+            <BagImage
+                visible={storedItems.length}
+                src={imageContent}
+                alt=""
+                draggable={false}
+                {...size}
+            />
+            <BagImage
+                visible={!storedItems.length}
+                src={emptyImageContent}
+                alt=""
+                draggable={false}
+                {...size}
+            />
+            <div className={`bag__label ${labelPosition}`}>{label}</div>
+        </BagWrapper>
+    );
+};
+
+export default memo(Bag);

+ 136 - 0
src/gameComponents/forms/BagFormFields.jsx

@@ -0,0 +1,136 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { Field } from "react-final-form";
+
+import Label from "../../ui/formUtils/Label";
+import { ImageField } from "../../mediaLibrary";
+
+import Hint from "../../ui/formUtils/Hint";
+import ColorPicker from "../../ui/formUtils/ColorPicker";
+
+import ActionList from "../ActionList";
+
+const interactions = ["reveal", "hide", "revealSelf", "stack"];
+
+const Form = ({ initialValues }) => {
+  const { t } = useTranslation();
+
+  return (
+    <>
+      <Label>
+        {t("Accepted families")}
+        <Field
+          name="families"
+          component="input"
+          format={(val) => val && val.join(",")}
+          parse={(val) => val && val.split(",").map((v) => v.trim())}
+          initialValue={initialValues.families || []}
+        />
+        <Hint>
+          {t(
+            "Let this field empty to accept all items, " +
+              "or set a comma separated list of families that will be accepted by the bag."
+          )}
+        </Hint>
+      </Label>
+      <Label>
+        {t("Label")}
+        <Field
+          name="label"
+          component="input"
+          initialValue={initialValues.label}
+        />
+      </Label>
+      <Label>
+        {t("Label position")}
+        <Field
+          name="labelPosition"
+          component="select"
+          initialValue={initialValues.labelPosition || "left"}
+          style={{ width: "10em" }}
+        >
+          <option value="left">{t("Left")}</option>
+          <option value="top">{t("Top")}</option>
+        </Field>
+      </Label>
+      <Label>
+        {t("Label background color")}
+        <Field
+          name="labelBackgroundColor"
+          component="input"
+          initialValue={initialValues.labelBackgroundColor || "#CCCCCC33"}
+        >
+          {({ input: { onChange, value } }) => (
+            <ColorPicker
+              value={value}
+              onChange={onChange}
+              disableAlpha={false}
+            />
+          )}
+        </Field>
+      </Label>
+      <Label>
+        {t("Width")}
+        <Field
+          name="width"
+          component="input"
+          initialValue={initialValues.width}
+        >
+          {(props) => <input {...props.input} type="number" />}
+        </Field>
+      </Label>
+      <Label>
+        {t("Height")}
+        <Field
+          name="height"
+          component="input"
+          initialValue={initialValues.height}
+        >
+          {(props) => <input {...props.input} type="number" />}
+        </Field>
+      </Label>
+      <Label>
+        {t("Empty bag image")}
+        <Field name="emptyBagImage" initialValue={initialValues.emptyBagImage}>
+          {({ input: { value, onChange } }) => {
+            return <ImageField value={value} onChange={onChange} />;
+          }}
+        </Field>
+      </Label>
+      <Label>
+        {t("Full bag image")}
+        <Field name="bagImage" initialValue={initialValues.bagImage}>
+          {({ input: { value, onChange } }) => {
+            return <ImageField value={value} onChange={onChange} />;
+          }}
+        </Field>
+      </Label>
+      
+      <Label>
+        {t("Counter visible")}
+        <Field
+          name="countervisible"
+          component="input"
+          type="checkbox"
+          initialValue={initialValues.countervisible}
+        />
+        <Hint>{t("Check it to make bag counter visible")}</Hint>
+      </Label>
+      <Label>
+        {t("Counter size")}
+        <Field
+          name="countersize"
+          component="input"
+          initialValue={initialValues.countersize}
+        >
+          {(props) => <input {...props.input} type="number" />}
+        </Field>
+      </Label>
+
+
+      
+    </>
+  );
+};
+
+export default Form;

+ 19 - 0
src/gameComponents/itemTemplates.js

@@ -20,6 +20,7 @@ import CheckerBoard from "./CheckerBoard";
 import Cylinder from "./Cylinder";
 import Generator from "./Generator";
 import Anchor from "./Anchor";
+import Bag from "./Bag";
 
 import ImageFormFields from "./forms/ImageFormFields";
 import CounterFormFields from "./forms/CounterFormFields";
@@ -39,6 +40,7 @@ import CheckerBoardFormFields from "./forms/CheckerBoardFormFields";
 import CylinderFormFields from "./forms/CylinderFormFields";
 import GeneratorFormFields from "./forms/GeneratorFormFields";
 import AnchorFormFields from "./forms/AnchorFormFields";
+import BagFormFields from "./forms/BagFormFields";
 
 const defaultDiceImages = () => [
   {
@@ -406,6 +408,23 @@ const itemTemplates = {
     template: { layer: 0 },
     resizeDirections: {},
   },
+  bag: {
+    component: Bag,
+    defaultActions: ["shuffleBag", "emptyBag", "clone", "lock", "remove"],
+    availableActions: [
+      "shuffleBag",
+      "emptyBag",
+      "clone",
+      "lock",
+      "remove",
+    ],
+    form: BagFormFields,
+    name: i18n.t("Bag"),
+    template: {
+      layer: -1,
+      countervisible: false,
+    },
+  },
 };
 
 export const itemLibrary = Object.keys(itemTemplates).map((key) => ({

+ 92 - 0
src/gameComponents/useGameItemActions.js

@@ -28,6 +28,9 @@ import rotateIcon from "../media/images/rotate.svg";
 import shuffleIcon from "../media/images/shuffle.svg";
 import tapIcon from "../media/images/tap.svg";
 import rollIcon from "../media/images/rolling-dices.svg";
+import emptyBagIcon from "../media/images/emptybag.svg"
+import bagExtractIcon from "../media/images/bagextract.svg"
+import shuffleBagIcon from "../media/images/shufflebag.svg"
 
 import flipAudio from "../media/audio/flip.wav?url";
 import rollAudio from "../media/audio/roll.wav?url";
@@ -40,6 +43,7 @@ export const useGameItemActions = () => {
     batchUpdateItems,
     removeItems,
     pushItem,
+    pushItems,
     reverseItemsOrder,
     swapItems,
     getItems,
@@ -463,6 +467,76 @@ export const useGameItemActions = () => {
     [getItemListOrSelected, pushItem]
   );
 
+  const shuffleBag = React.useCallback(
+    async (itemIds) => {
+      const [ids, items] = await getItemListOrSelected(itemIds);
+
+      batchUpdateItems(ids, (item) => {
+        /* Prevent modification if item doesn't store other items */
+        if (item.storedItems) {
+          const elem = getItemElement(item.id);
+          elem.firstChild.className = "hvr-wobble-horizontal";
+          const shuffledItems = shuffleArray([...item.storedItems]);
+          return {
+            ...item,
+            storedItems: shuffledItems,
+          };
+        } else {
+          return item;
+        }
+      });
+
+      //play audio only if at least a component has been shuffled
+      let shuffled = false;
+      items.forEach((item) => {
+        if (item.storedItems) {
+          shuffled = true;
+        }
+      });
+
+      if (shuffled)
+        playAudio(shuffleAudio, 0.5);
+    },
+    [getItemListOrSelected, batchUpdateItems]
+  );
+
+  const emptyBag = React.useCallback(
+    async (itemIds) => {
+      const [ids, items] = await getItemListOrSelected(itemIds);
+
+      items.forEach((item) => {
+        if (item.storedItems) {
+          const elem = getItemElement(item.id);
+
+          const extractedItems = item.storedItems.map((sItem) => {
+            const extractedItem = {
+              ...sItem,
+              x: item.x + elem.clientWidth / 3.0,
+              y: item.y + elem.clientHeight / 3.0,
+            }
+
+            return extractedItem;
+          })
+
+          pushItems(extractedItems);
+        }
+      });
+
+      batchUpdateItems(ids, (item) => {
+        /* Prevent modification if item doesn't store other items */
+        if (item.storedItems) {
+          return {
+            ...item,
+            storedItems: [],
+          };
+        } else {
+          return item;
+        }
+      });
+    },
+    [getItemListOrSelected, batchUpdateItems, pushItems]
+  );
+
   const actionMap = React.useMemo(() => {
     const actions = {
       flip: {
@@ -671,6 +745,20 @@ export const useGameItemActions = () => {
         disableDblclick: true,
         icon: deleteIcon,
       },
+      shuffleBag: {
+        action: () => shuffleBag,
+        label: t("Shuffle Bag"),
+        shortcut: "b",
+        disableDblclick: true,
+        icon: shuffleBagIcon,
+      },
+      emptyBag: {
+        action: () => emptyBag,
+        label: t("Empty Bag"),
+        shortcut: "x",
+        disableDblclick: true,
+        icon: emptyBagIcon,
+      },
     };
 
     return Object.fromEntries(
@@ -700,6 +788,8 @@ export const useGameItemActions = () => {
     toggleFlipSelf,
     toggleLock,
     toggleTap,
+    shuffleBag,
+    emptyBag,
   ]);
 
   return {
@@ -716,6 +806,8 @@ export const useGameItemActions = () => {
     toggleTap,
     snapToPoint,
     shuffle: shuffleItems,
+    shuffleBag,
+    emptyBag,
     actionMap,
   };
 };

+ 14 - 0
src/media/images/bagextract.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<polyline fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="bevel" stroke-miterlimit="10" points="40,43 32,51 
+	24,43 "/>
+<g>
+	<line fill="none" stroke="#FFFFFF" stroke-width="2" stroke-miterlimit="10" x1="32" y1="51" x2="32" y2="31"/>
+</g>
+<polygon fill="none" stroke="#FFFFFF" stroke-width="2" stroke-miterlimit="10" points="44,18 54,18 54,63 10,63 10,18 20,18 "/>
+<path fill="none" stroke="#FFFFFF" stroke-width="2" stroke-miterlimit="10" d="M22,24V11c0-5.523,4.477-10,10-10s10,4.477,10,10v13
+	"/>
+</svg>

+ 10 - 0
src/media/images/emptybag.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<polygon fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" points="44,18 54,18 54,63 10,63 10,18 20,18 "/>
+<line fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" x1="39" y1="49" x2="25" y2="35"/>
+<line fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" x1="25" y1="49" x2="39" y2="35"/>
+<path fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" d="M22,24V11c0-5.523,4.477-10,10-10s10,4.477,10,10v13"/>
+</svg>

+ 15 - 0
src/media/images/shufflebag.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<path fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" d="M24,42c0,4.418,3.582,9,8,9h4"/>
+<polyline fill="none" stroke="#FFFFFF" stroke-width="5" stroke-linejoin="bevel" stroke-miterlimit="10" points="33,55 36,51 
+	33,47 "/>
+<path fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" d="M42,42c0-4.418-3.582-9-8-9h-4"/>
+<polyline fill="none" stroke="#FFFFFF" stroke-width="5" stroke-linejoin="bevel" stroke-miterlimit="10" points="33,29 30,33 
+	33,37 "/>
+<polygon fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" points="44,18 54,18 54,63 10,63 10,18 20,18 "/>
+<path fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" d="M22,24V11c0-5.523,4.477-10,10-10s10,4.477,10,10v13
+	"/>
+</svg>