Share chat url
Modal for video owner (and instance's moderators) that allows to generate a link to the chat. So you can - for example - obtain the url to use for OBS integration. WIP
This commit is contained in:
parent
148b28ef84
commit
566681150b
@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Builtin Prosody: Readonly mode. You can open the chat in readonly mode. Could be used to integrate in OBS for example.
|
* Builtin Prosody:
|
||||||
|
* Readonly mode. You can open the chat in readonly mode. Could be used to integrate in OBS for example.
|
||||||
|
* Share chat url: modal for video owner (and instance's moderators) that allows to generate a link to the chat. So you can - for example - obtain the url to use for OBS integration.
|
||||||
* Builtin Prosody: you can now allow «external XMPP components» to connect. This can be used for exemple to connect bots or bridges. For now, only connections from localhost will be allowed.
|
* Builtin Prosody: you can now allow «external XMPP components» to connect. This can be used for exemple to connect bots or bridges. For now, only connections from localhost will be allowed.
|
||||||
|
|
||||||
### Minor changes and fixes
|
### Minor changes and fixes
|
||||||
|
@ -53,6 +53,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.peertube-plugin-livechat-multi-button-secondary {
|
.peertube-plugin-livechat-multi-button-secondary {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
border-left: 1px solid currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peertube-plugin-livechat-multi-button-last-secondary {
|
||||||
border-top-left-radius: 0 !important;
|
border-top-left-radius: 0 !important;
|
||||||
border-bottom-left-radius: 0 !important;
|
border-bottom-left-radius: 0 !important;
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import type { ChatType } from 'shared/lib/types'
|
import type { ChatType } from 'shared/lib/types'
|
||||||
import { videoHasWebchat } from 'shared/lib/video'
|
import { videoHasWebchat } from 'shared/lib/video'
|
||||||
import { AutoColors, isAutoColorsAvailable, areAutoColorsValid } from 'shared/lib/autocolors'
|
import { AutoColors, isAutoColorsAvailable, areAutoColorsValid } from 'shared/lib/autocolors'
|
||||||
import { closeSVG, openBlankChatSVG, openChatSVG, SVGButton } from './videowatch/buttons'
|
import { logger } from './videowatch/logger'
|
||||||
|
import { closeSVG, openBlankChatSVG, openChatSVG, shareChatUrlSVG } from './videowatch/buttons'
|
||||||
|
import { displayButton, displayButtonOptions } from './videowatch/button'
|
||||||
|
import { shareChatUrl } from './videowatch/share'
|
||||||
|
|
||||||
interface VideoWatchLoadedHookOptions {
|
interface VideoWatchLoadedHookOptions {
|
||||||
videojs: any
|
videojs: any
|
||||||
@ -9,14 +12,8 @@ interface VideoWatchLoadedHookOptions {
|
|||||||
playlist?: any
|
playlist?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
function register ({ registerHook, peertubeHelpers }: RegisterOptions): void {
|
function register (registerOptions: RegisterOptions): void {
|
||||||
const logger = {
|
const { registerHook, peertubeHelpers } = registerOptions
|
||||||
log: (s: string) => console.log('[peertube-plugin-livechat] ' + s),
|
|
||||||
info: (s: string) => console.info('[peertube-plugin-livechat] ' + s),
|
|
||||||
error: (s: string) => console.error('[peertube-plugin-livechat] ' + s),
|
|
||||||
warn: (s: string) => console.warn('[peertube-plugin-livechat] ' + s)
|
|
||||||
}
|
|
||||||
|
|
||||||
let settings: any = {}
|
let settings: any = {}
|
||||||
|
|
||||||
function getBaseRoute (): string {
|
function getBaseRoute (): string {
|
||||||
@ -125,58 +122,22 @@ function register ({ registerHook, peertubeHelpers }: RegisterOptions): void {
|
|||||||
return autocolors
|
return autocolors
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayButton (
|
async function insertChatDom (
|
||||||
buttonContainer: HTMLElement,
|
container: HTMLElement, video: Video, showOpenBlank: boolean, showShareUrlButton: boolean
|
||||||
name: string,
|
): Promise<void> {
|
||||||
label: string,
|
|
||||||
callback: () => void | boolean,
|
|
||||||
icon?: SVGButton,
|
|
||||||
additionalClasses?: string[]
|
|
||||||
): void {
|
|
||||||
const button = document.createElement('a')
|
|
||||||
button.classList.add(
|
|
||||||
'orange-button', 'peertube-button-link',
|
|
||||||
'peertube-plugin-livechat-button',
|
|
||||||
'peertube-plugin-livechat-button-' + name
|
|
||||||
)
|
|
||||||
if (additionalClasses) {
|
|
||||||
for (let i = 0; i < additionalClasses.length; i++) {
|
|
||||||
button.classList.add(additionalClasses[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button.onclick = callback
|
|
||||||
if (icon) {
|
|
||||||
try {
|
|
||||||
const svg = icon()
|
|
||||||
const tmp = document.createElement('span')
|
|
||||||
tmp.innerHTML = svg.trim()
|
|
||||||
const svgDom = tmp.firstChild
|
|
||||||
if (svgDom) {
|
|
||||||
button.prepend(svgDom)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Failed to generate the ' + name + ' button: ' + (err as string))
|
|
||||||
}
|
|
||||||
|
|
||||||
button.setAttribute('title', label)
|
|
||||||
} else {
|
|
||||||
button.textContent = label
|
|
||||||
}
|
|
||||||
buttonContainer.append(button)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function insertChatDom (container: HTMLElement, video: Video, showOpenBlank: boolean): Promise<void> {
|
|
||||||
logger.log('Adding livechat in the DOM...')
|
logger.log('Adding livechat in the DOM...')
|
||||||
const p = new Promise<void>((resolve, reject) => {
|
const p = new Promise<void>((resolve, reject) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
Promise.all([
|
Promise.all([
|
||||||
peertubeHelpers.translate('Open chat'),
|
peertubeHelpers.translate('Open chat'),
|
||||||
peertubeHelpers.translate('Open chat in a new window'),
|
peertubeHelpers.translate('Open chat in a new window'),
|
||||||
peertubeHelpers.translate('Close chat')
|
peertubeHelpers.translate('Close chat'),
|
||||||
|
peertubeHelpers.translate('Share link')
|
||||||
]).then(labels => {
|
]).then(labels => {
|
||||||
const labelOpen = labels[0]
|
const labelOpen = labels[0]
|
||||||
const labelOpenBlank = labels[1]
|
const labelOpenBlank = labels[1]
|
||||||
const labelClose = labels[2]
|
const labelClose = labels[2]
|
||||||
|
const labelShareUrl = labels[3]
|
||||||
|
|
||||||
const iframeUri = getIframeUri(video)
|
const iframeUri = getIframeUri(video)
|
||||||
if (!iframeUri) {
|
if (!iframeUri) {
|
||||||
@ -187,27 +148,66 @@ function register ({ registerHook, peertubeHelpers }: RegisterOptions): void {
|
|||||||
buttonContainer.classList.add('peertube-plugin-livechat-buttons')
|
buttonContainer.classList.add('peertube-plugin-livechat-buttons')
|
||||||
container.append(buttonContainer)
|
container.append(buttonContainer)
|
||||||
|
|
||||||
|
// Here are buttons that are magically merged
|
||||||
|
const groupButtons: displayButtonOptions[] = []
|
||||||
|
groupButtons.push({
|
||||||
|
buttonContainer,
|
||||||
|
name: 'open',
|
||||||
|
label: labelOpen,
|
||||||
|
callback: () => openChat(video),
|
||||||
|
icon: openChatSVG,
|
||||||
|
additionalClasses: []
|
||||||
|
})
|
||||||
if (showOpenBlank) {
|
if (showOpenBlank) {
|
||||||
displayButton(
|
groupButtons.push({
|
||||||
buttonContainer, 'open',
|
buttonContainer,
|
||||||
labelOpen, () => openChat(video),
|
name: 'openblank',
|
||||||
openChatSVG,
|
label: labelOpenBlank,
|
||||||
['peertube-plugin-livechat-multi-button-main']
|
callback: () => {
|
||||||
)
|
|
||||||
displayButton(
|
|
||||||
buttonContainer, 'openblank',
|
|
||||||
labelOpenBlank, () => {
|
|
||||||
closeChat()
|
closeChat()
|
||||||
window.open(iframeUri)
|
window.open(iframeUri)
|
||||||
},
|
},
|
||||||
openBlankChatSVG,
|
icon: openBlankChatSVG,
|
||||||
['peertube-plugin-livechat-multi-button-secondary']
|
additionalClasses: []
|
||||||
)
|
})
|
||||||
} else {
|
}
|
||||||
displayButton(buttonContainer, 'open', labelOpen, () => openChat(video), openChatSVG)
|
if (showShareUrlButton) {
|
||||||
|
groupButtons.push({
|
||||||
|
buttonContainer,
|
||||||
|
name: 'shareurl',
|
||||||
|
label: labelShareUrl,
|
||||||
|
callback: () => {
|
||||||
|
shareChatUrl(registerOptions)
|
||||||
|
},
|
||||||
|
icon: shareChatUrlSVG,
|
||||||
|
additionalClasses: []
|
||||||
|
})
|
||||||
}
|
}
|
||||||
displayButton(buttonContainer, 'close', labelClose, () => closeChat(), closeSVG)
|
|
||||||
|
|
||||||
|
// If more than one groupButtons:
|
||||||
|
// - the first must have class 'peertube-plugin-livechat-multi-button-main'
|
||||||
|
// - middle ones must have 'peertube-plugin-livechat-multi-button-secondary'
|
||||||
|
// - the last must have 'peertube-plugin-livechat-multi-button-last-secondary'
|
||||||
|
if (groupButtons.length > 1) {
|
||||||
|
groupButtons[0].additionalClasses?.push('peertube-plugin-livechat-multi-button-main')
|
||||||
|
for (let i = 1; i < groupButtons.length - 1; i++) { // middle
|
||||||
|
groupButtons[i].additionalClasses?.push('peertube-plugin-livechat-multi-button-secondary')
|
||||||
|
}
|
||||||
|
groupButtons[groupButtons.length - 1]
|
||||||
|
.additionalClasses?.push('peertube-plugin-livechat-multi-button-last-secondary')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const button of groupButtons) {
|
||||||
|
displayButton(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayButton({
|
||||||
|
buttonContainer,
|
||||||
|
name: 'close',
|
||||||
|
label: labelClose,
|
||||||
|
callback: () => closeChat(),
|
||||||
|
icon: closeSVG
|
||||||
|
})
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -301,7 +301,12 @@ function register ({ registerHook, peertubeHelpers }: RegisterOptions): void {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
insertChatDom(container as HTMLElement, video, !!settings['chat-open-blank']).then(() => {
|
let showShareUrlButton: boolean = false
|
||||||
|
if (settings['chat-type'] === 'builtin-prosody') {
|
||||||
|
// FIXME: showShareUrlButton should only be true for video owner and instance moderators.
|
||||||
|
showShareUrlButton = true
|
||||||
|
}
|
||||||
|
insertChatDom(container as HTMLElement, video, !!settings['chat-open-blank'], showShareUrlButton).then(() => {
|
||||||
if (settings['chat-auto-display']) {
|
if (settings['chat-auto-display']) {
|
||||||
openChat(video)
|
openChat(video)
|
||||||
} else if (container) {
|
} else if (container) {
|
||||||
|
56
client/videowatch/button.ts
Normal file
56
client/videowatch/button.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import type { SVGButton } from './buttons'
|
||||||
|
import { logger } from './logger'
|
||||||
|
|
||||||
|
interface displayButtonOptions {
|
||||||
|
buttonContainer: HTMLElement
|
||||||
|
name: string
|
||||||
|
label: string
|
||||||
|
callback: () => void | boolean
|
||||||
|
icon?: SVGButton
|
||||||
|
additionalClasses?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayButton ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
callback,
|
||||||
|
buttonContainer,
|
||||||
|
additionalClasses,
|
||||||
|
icon
|
||||||
|
}: displayButtonOptions): void {
|
||||||
|
const button = document.createElement('a')
|
||||||
|
button.classList.add(
|
||||||
|
'orange-button', 'peertube-button-link',
|
||||||
|
'peertube-plugin-livechat-button',
|
||||||
|
'peertube-plugin-livechat-button-' + name
|
||||||
|
)
|
||||||
|
if (additionalClasses) {
|
||||||
|
for (let i = 0; i < additionalClasses.length; i++) {
|
||||||
|
button.classList.add(additionalClasses[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button.onclick = callback
|
||||||
|
if (icon) {
|
||||||
|
try {
|
||||||
|
const svg = icon()
|
||||||
|
const tmp = document.createElement('span')
|
||||||
|
tmp.innerHTML = svg.trim()
|
||||||
|
const svgDom = tmp.firstChild
|
||||||
|
if (svgDom) {
|
||||||
|
button.prepend(svgDom)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to generate the ' + name + ' button: ' + (err as string))
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setAttribute('title', label)
|
||||||
|
} else {
|
||||||
|
button.textContent = label
|
||||||
|
}
|
||||||
|
buttonContainer.append(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
displayButtonOptions,
|
||||||
|
displayButton
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable max-len */
|
||||||
type SVGButton = () => string
|
type SVGButton = () => string
|
||||||
|
|
||||||
const closeSVG: SVGButton = () => {
|
const closeSVG: SVGButton = () => {
|
||||||
@ -84,9 +85,24 @@ const openBlankChatSVG: SVGButton = () => {
|
|||||||
</svg>`
|
</svg>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shareChatUrlSVG: SVGButton = () => {
|
||||||
|
// This content comes from the file public/image/url.svg, after svgo cleaning.
|
||||||
|
// To get the formated content, you can do:
|
||||||
|
// xmllint dist/client/images/url.svg --format
|
||||||
|
// Then replace the color by `currentColor`
|
||||||
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 4.233 4.233">
|
||||||
|
<g style="stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none">
|
||||||
|
<path style="opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m6.556-.435 1.132-1.132c.269-.268.618-.351.784-.186L9.867-.357c.166.165.083.515-.186.784L7.418 2.69c-.269.269-.618.352-.784.186l-.698-.697-.39-.407" transform="matrix(.45208 0 0 .45208 -.73 1.423)"/>
|
||||||
|
<path style="opacity:.998;fill:none;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M6.038 3.505 4.906 4.637c-.268.268-.618.351-.784.186L2.727 3.427c-.166-.165-.083-.515.186-.784L5.176.38c.27-.269.619-.352.784-.186l.698.697.39.407" transform="matrix(.45208 0 0 .45208 -.73 1.423)"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
closeSVG,
|
closeSVG,
|
||||||
openChatSVG,
|
openChatSVG,
|
||||||
openBlankChatSVG,
|
openBlankChatSVG,
|
||||||
|
shareChatUrlSVG,
|
||||||
SVGButton
|
SVGButton
|
||||||
}
|
}
|
||||||
|
10
client/videowatch/logger.ts
Normal file
10
client/videowatch/logger.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const logger = {
|
||||||
|
log: (s: string) => console.log('[peertube-plugin-livechat] ' + s),
|
||||||
|
info: (s: string) => console.info('[peertube-plugin-livechat] ' + s),
|
||||||
|
error: (s: string) => console.error('[peertube-plugin-livechat] ' + s),
|
||||||
|
warn: (s: string) => console.warn('[peertube-plugin-livechat] ' + s)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
logger
|
||||||
|
}
|
11
client/videowatch/share.ts
Normal file
11
client/videowatch/share.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
async function shareChatUrl ({ peertubeHelpers }: RegisterOptions): Promise<void> {
|
||||||
|
peertubeHelpers.showModal({
|
||||||
|
title: 'TODO',
|
||||||
|
content: '<p>TODO</p>',
|
||||||
|
close: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
shareChatUrl
|
||||||
|
}
|
148
public/images/url.svg
Normal file
148
public/images/url.svg
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 4.2333332 4.2333334"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1428"
|
||||||
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||||
|
sodipodi:docname="url.svg"
|
||||||
|
inkscape:export-xdpi="9.6000004"
|
||||||
|
inkscape:export-ydpi="9.6000004">
|
||||||
|
<defs
|
||||||
|
id="defs1422">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5158"
|
||||||
|
osb:paint="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#7d7d7d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5156" />
|
||||||
|
</linearGradient>
|
||||||
|
<filter
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
inkscape:label="Drop Shadow"
|
||||||
|
id="filter1150">
|
||||||
|
<feFlood
|
||||||
|
flood-opacity="1"
|
||||||
|
flood-color="rgb(0,0,0)"
|
||||||
|
result="flood"
|
||||||
|
id="feFlood1140" />
|
||||||
|
<feComposite
|
||||||
|
in="flood"
|
||||||
|
in2="SourceGraphic"
|
||||||
|
operator="in"
|
||||||
|
result="composite1"
|
||||||
|
id="feComposite1142" />
|
||||||
|
<feGaussianBlur
|
||||||
|
in="composite1"
|
||||||
|
stdDeviation="0.2"
|
||||||
|
result="blur"
|
||||||
|
id="feGaussianBlur1144" />
|
||||||
|
<feOffset
|
||||||
|
dx="0"
|
||||||
|
dy="0"
|
||||||
|
result="offset"
|
||||||
|
id="feOffset1146" />
|
||||||
|
<feComposite
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="offset"
|
||||||
|
operator="over"
|
||||||
|
result="composite2"
|
||||||
|
id="feComposite1148" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
inkscape:label="Drop Shadow"
|
||||||
|
id="filter1174">
|
||||||
|
<feFlood
|
||||||
|
flood-opacity="1"
|
||||||
|
flood-color="rgb(0,0,0)"
|
||||||
|
result="flood"
|
||||||
|
id="feFlood1164" />
|
||||||
|
<feComposite
|
||||||
|
in="flood"
|
||||||
|
in2="SourceGraphic"
|
||||||
|
operator="in"
|
||||||
|
result="composite1"
|
||||||
|
id="feComposite1166" />
|
||||||
|
<feGaussianBlur
|
||||||
|
in="composite1"
|
||||||
|
stdDeviation="0.2"
|
||||||
|
result="blur"
|
||||||
|
id="feGaussianBlur1168" />
|
||||||
|
<feOffset
|
||||||
|
dx="0"
|
||||||
|
dy="0"
|
||||||
|
result="offset"
|
||||||
|
id="feOffset1170" />
|
||||||
|
<feComposite
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="offset"
|
||||||
|
operator="over"
|
||||||
|
result="composite2"
|
||||||
|
id="feComposite1172" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.627417"
|
||||||
|
inkscape:cx="12.175315"
|
||||||
|
inkscape:cy="11.281577"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1851"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
inkscape:window-x="1680"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
units="px"
|
||||||
|
showguides="true" />
|
||||||
|
<metadata
|
||||||
|
id="metadata1425">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
id="g1910"
|
||||||
|
transform="matrix(0.45207703,0,0,0.45207703,-0.73010762,1.4227)"
|
||||||
|
style="stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none">
|
||||||
|
<path
|
||||||
|
id="rect1876-6-3"
|
||||||
|
style="opacity:0.998;fill:none;fill-opacity:1;stroke:#deddda;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 6.5562019,-0.43483068 7.6880252,-1.566654 c 0.2686526,-0.2686526 0.6182653,-0.3515998 0.7838849,-0.1859801 l 1.3954984,1.39549832 c 0.1656205,0.16561966 0.08268,0.51523254 -0.1859796,0.78388514 L 7.417782,2.6903959 C 7.1491294,2.9590485 6.7995165,3.0419954 6.6338969,2.8763758 L 5.9361477,2.1786266 5.5457576,1.7722389"
|
||||||
|
sodipodi:nodetypes="csssssscc" />
|
||||||
|
<path
|
||||||
|
id="rect1876-6-3-3"
|
||||||
|
style="opacity:0.998;fill:none;fill-opacity:1;stroke:#deddda;stroke-width:1.17052;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 6.0379981,3.5049564 4.9061748,4.6367797 C 4.6375222,4.9054323 4.2879095,4.9883795 4.1222899,4.8227598 L 2.7267915,3.4272615 C 2.561171,3.2616418 2.6441115,2.9120289 2.9127711,2.6433763 L 5.176418,0.37972983 c 0.2686526,-0.2686526 0.6182655,-0.3515995 0.7838851,-0.1859799 l 0.6977492,0.6977492 0.3903901,0.40638767"
|
||||||
|
sodipodi:nodetypes="csssssscc" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
Loading…
x
Reference in New Issue
Block a user