Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add playlist support #6

Merged
merged 4 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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