Skip to content

Commit

Permalink
Merge pull request #6 from 10up/feature/audio-playlist
Browse files Browse the repository at this point in the history
WIP: Add playlist support
  • Loading branch information
fabiankaegy authored Nov 12, 2021
2 parents 81c3ee3 + 3829424 commit 8a36ae5
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 31 deletions.
27 changes: 24 additions & 3 deletions block.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,31 @@
"name": "tenup/webamp-block",
"version": "0.1.0",
"title": "Webamp Block",
"category": "widgets",
"icon": "smiley",
"description": "A Winamp-styled audio block for all your retro music player needs.",
"category": "media",
"icon": "playlist-audio",
"description": "Example block written with ESNext standard and JSX support – build step required.",
"attributes": {
"audio": {
"type": "array",
"default": [],
"source": "query",
"selector": ".blocks-audio-grid",
"query": {
"url": {
"type": "string",
"source": "attribute",
"selector": "audio",
"attribute": "src"
}
}
},
"ids": {
"type": "array",
"items": {
"type": "number"
},
"default": []
},
"currentSkin": {
"type": "string",
"default": ""
Expand Down
54 changes: 54 additions & 0 deletions src/audio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { __experimentalUseInnerBlocksProps as useInnerBlocksProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import { View } from '@wordpress/primitives';

/**
* Internal dependencies
*/
import WebAmp from './webamp';

const allowedBlocks = [ 'core/audio' ];

export const Audio = ( props ) => {
const {
audio,
currentSkin,
mediaPlaceholder,
blockProps,
} = props;

const { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps, {
allowedBlocks,
renderAppender: false,
} );

return (
<figure
{ ...innerBlocksProps }
className={ classnames(
blockProps.className,
'blocks-audio-list',
) }
>
<WebAmp audio={ audio } currentSkin={ currentSkin } />

{ children }

<View
className="blocks-webamp-media-placeholder-wrapper"
>
{ mediaPlaceholder }
</View>
</figure>
);
};

export default Audio;
188 changes: 180 additions & 8 deletions src/edit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
/**
* External dependencies
*/
import { concat } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { createBlock } from '@wordpress/blocks';
import { PanelBody, TextControl } from '@wordpress/components';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import {
useBlockProps,
InspectorControls,
store as blockEditorStore,
MediaPlaceholder
} from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { Platform, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import './editor.scss';
import Audio from './audio';

const ALLOWED_MEDIA_TYPES = [ 'audio' ];
const PLACEHOLDER_TEXT = Platform.isNative
? __( 'ADD MEDIA', 'webamp-block' )
: __( 'Drag audio files, upload new ones or select files from your library.', 'webamp-block' );

/**
* The edit function describes the structure of your block in the context of the
Expand All @@ -11,13 +37,153 @@ import './editor.scss';
*
* @return {WPElement} Element to render.
*/
export default function Edit({
attributes: { currentSkin },
export default function Edit( {
attributes,
clientId,
isSelected,
noticeOperations,
noticeUI,
setAttributes,
}) {
} ) {

const { currentSkin } = attributes;
const blockProps = useBlockProps();
const { replaceInnerBlocks } = useDispatch( blockEditorStore );

const innerBlockAudio = useSelect(
( select ) => {
return select( blockEditorStore ).getBlock( clientId )?.innerBlocks;
},
[ clientId ]
);

const audio = useMemo(
() =>
innerBlockAudio?.map( ( block ) => ( {
clientId: block.clientId,
id: block.attributes.id,
url: block.attributes.src,
attributes: block.attributes,
fromSavedContent: Boolean( block.originalContent ),
} ) ),
[ innerBlockAudio ]
);

const hasAudio = !! audio.length;
const hasAudioIds = hasAudio && audio.some( ( audioItem ) => !! audioItem.id );
const audioUploading = audio.some(
( audioItem ) => ! audioItem.id && audioItem.url?.indexOf( 'blob:' ) === 0
);

function isValidFileType( file ) {
return (
ALLOWED_MEDIA_TYPES.some(
( mediaType ) => file.type?.indexOf( mediaType ) === 0
) || file.url?.indexOf( 'blob:' ) === 0
);
}

function onSelectAudio( selectedAudio ) {
const newFileUploads =
Object.prototype.toString.call( selectedAudio ) ===
'[object FileList]';

const audioArray = newFileUploads
? Array.from( selectedAudio ).map( ( file ) => {
return file;
} )
: selectedAudio;

if ( ! audioArray.every( isValidFileType ) ) {
noticeOperations.removeAllNotices();
noticeOperations.createErrorNotice(
__( 'All files need to be in audio formats', 'webamp-block' ),
{ id: 'webamp-upload-invalid-file' }
);
}

const processedAudio = audioArray
.filter( ( file ) => file.url || isValidFileType( file ) )
.map( ( file ) => {
return file;
} );

// Because we are reusing existing innerAudio blocks any reordering
// done in the media library will be lost so we need to reapply that ordering
// once the new audio blocks are merged in with existing.
const newOrderMap = processedAudio.reduce(
( result, media, index ) => (
( result[ media.id ] = index ), result
),
{}
);

const existingAudioBlocks = ! newFileUploads
? innerBlockAudio.filter( ( block ) =>
processedAudio.find(
( img ) => img.id === block.attributes.id
)
)
: innerBlockAudio;

const newAudioList = processedAudio.filter(
( media ) =>
! existingAudioBlocks.find(
( existingAudio ) => media.id === existingAudio.attributes.id
)
);

const newBlocks = newAudioList.map( ( audio ) => {
return createBlock( 'core/audio', {
id: audio.id,
src: audio.url,
} );
} );

replaceInnerBlocks(
clientId,
concat( existingAudioBlocks, newBlocks ).sort(
( a, b ) =>
newOrderMap[ a.attributes.id ] -
newOrderMap[ b.attributes.id ]
)
);
}

function onUploadError( message ) {
noticeOperations.removeAllNotices();
noticeOperations.createErrorNotice( message );
}

const mediaPlaceholder = (
<MediaPlaceholder
addToGallery={ hasAudioIds }
icon={ ! hasAudio && "format-audio" }
onSelect={ onSelectAudio }
isAppender={ hasAudio }
disableMediaButtons={
( hasAudio && ! isSelected ) || audioUploading
}
labels={ {
title: ! hasAudio && __( 'Audio', 'webamp-block' ),
instructions: ! hasAudio && PLACEHOLDER_TEXT,
} }
accept="audio/*"
allowedTypes={ ALLOWED_MEDIA_TYPES }
multiple={ true }
value={ hasAudioIds ? audio : {} }
onError={ onUploadError }
notices={ hasAudio ? undefined : noticeUI }
/>
);

if ( ! hasAudio ) {
return <div { ...blockProps }>{ mediaPlaceholder }</div>;
}

return (
<p { ...useBlockProps() }>
<InspectorControls key="skin">
<>
<InspectorControls>
<PanelBody title={ __( 'Skin', 'webamp-block' ) }>
<TextControl
label={ __( 'Skin URL', 'webamp-block' ) }
Expand All @@ -27,7 +193,13 @@ export default function Edit({
/>
</PanelBody>
</InspectorControls>
{ __( 'Webamp Block – hello from the editor!', 'webamp-block' ) }
</p>
{ noticeUI }
<Audio
audio={ audio }
currentSkin={ currentSkin }
mediaPlaceholder={ mediaPlaceholder }
blockProps={ blockProps }
/>
</>
);
}
34 changes: 19 additions & 15 deletions src/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,31 @@ import domReady from "@wordpress/dom-ready";
domReady(() => {
const container = document.querySelector(".wp-block-tenup-webamp-block");

// Ensure our container exists
if (!container) {
return;
}

const skin = container.dataset.skin || "";
// Ensure we actually have some audio to play
const audioElements = container.querySelectorAll("audio");
if (!audioElements) {
return;
}

const options = {
initialTracks: [
{
metaData: {
artist: "DJ Mike Llama",
title: "Llama Whippin' Intro",
},
// NOTE: Your audio file must be served from the same domain as your HTML
// file, or served with permissive CORS HTTP headers:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
url:
"https://cdn.jsdelivr.net/gh/captbaritone/webamp@43434d82cfe0e37286dbbe0666072dc3190a83bc/mp3/llama-2.91.mp3",
duration: 5.322286,
},
],
initialTracks: []
};

audioElements.forEach(audio => options.initialTracks.push( { url: audio.src } ));

// Ensure our audio tracks were added correctly
if (options.initialTracks.length === 0) {
return;
}

const skin = container.dataset.skin || "";

// Add the custom skin if it was set
if (skin) {
const match = skin.match(/(?:https?:)?(?:\/\/)?skins\.webamp\.org\/skin\/(\w+)\/(?:.*)?/);
if (match && match.length === 2) {
Expand All @@ -35,5 +38,6 @@ domReady(() => {
}
};

// Render the player
new Webamp(options).renderWhenReady(container);
});
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ import "./style.scss";
*/
import Edit from "./edit";
import save from "./save";
import metadata from '../block.json';
import { LightningIcon } from './icon';

/**
* Every block starts by registering a new block type definition.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
*/
registerBlockType("tenup/webamp-block", {

registerBlockType( metadata, {
icon: LightningIcon,
/**
* @see ./edit.js
Expand All @@ -38,4 +38,4 @@ registerBlockType("tenup/webamp-block", {
* @see ./save.js
*/
save,
});
} );
8 changes: 6 additions & 2 deletions src/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
*/
import { useBlockProps } from "@wordpress/block-editor";
import { InnerBlocks, useBlockProps } from "@wordpress/block-editor";

/**
* The save function defines the way in which the different attributes should
Expand All @@ -16,5 +16,9 @@ import { useBlockProps } from "@wordpress/block-editor";
* @return {WPElement} Element to render.
*/
export default function save({ attributes }) {
return <div {...useBlockProps.save()} data-skin={attributes.currentSkin}></div>;
return (
<figure { ...useBlockProps.save() } data-skin={ attributes.currentSkin }>
<InnerBlocks.Content />
</figure>
);
}
Loading

0 comments on commit 8a36ae5

Please sign in to comment.